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 @tag capture_log: true
455 test "it inserts an incoming activity into the database" <>
456 "even if we can't fetch the user but have it in our db",
460 ap_id: "https://mastodon.example.org/users/raymoo",
463 last_refreshed_at: nil
467 File.read!("test/fixtures/mastodon-post-activity.json")
469 |> Map.put("actor", user.ap_id)
470 |> put_in(["object", "attridbutedTo"], user.ap_id)
474 |> assign(:valid_signature, true)
475 |> put_req_header("content-type", "application/activity+json")
476 |> post("/inbox", data)
478 assert "ok" == json_response(conn, 200)
480 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
481 assert Activity.get_by_ap_id(data["id"])
484 test "it clears `unreachable` federation status of the sender", %{conn: conn} do
485 data = File.read!("test/fixtures/mastodon-post-activity.json") |> Poison.decode!()
487 sender_url = data["actor"]
488 Instances.set_consistently_unreachable(sender_url)
489 refute Instances.reachable?(sender_url)
493 |> assign(:valid_signature, true)
494 |> put_req_header("content-type", "application/activity+json")
495 |> post("/inbox", data)
497 assert "ok" == json_response(conn, 200)
498 assert Instances.reachable?(sender_url)
501 test "accept follow activity", %{conn: conn} do
502 Pleroma.Config.put([:instance, :federating], true)
503 relay = Relay.get_actor()
505 assert {:ok, %Activity{} = activity} = Relay.follow("https://relay.mastodon.host/actor")
507 followed_relay = Pleroma.User.get_by_ap_id("https://relay.mastodon.host/actor")
508 relay = refresh_record(relay)
511 File.read!("test/fixtures/relay/accept-follow.json")
512 |> String.replace("{{ap_id}}", relay.ap_id)
513 |> String.replace("{{activity_id}}", activity.data["id"])
517 |> assign(:valid_signature, true)
518 |> put_req_header("content-type", "application/activity+json")
519 |> post("/inbox", accept)
520 |> json_response(200)
522 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
524 assert Pleroma.FollowingRelationship.following?(
529 Mix.shell(Mix.Shell.Process)
532 Mix.shell(Mix.Shell.IO)
535 :ok = Mix.Tasks.Pleroma.Relay.run(["list"])
536 assert_receive {:mix_shell, :info, ["relay.mastodon.host"]}
539 @tag capture_log: true
540 test "without valid signature, " <>
541 "it only accepts Create activities and requires enabled federation",
543 data = File.read!("test/fixtures/mastodon-post-activity.json") |> Poison.decode!()
544 non_create_data = File.read!("test/fixtures/mastodon-announce.json") |> Poison.decode!()
546 conn = put_req_header(conn, "content-type", "application/activity+json")
548 Config.put([:instance, :federating], false)
551 |> post("/inbox", data)
552 |> json_response(403)
555 |> post("/inbox", non_create_data)
556 |> json_response(403)
558 Config.put([:instance, :federating], true)
560 ret_conn = post(conn, "/inbox", data)
561 assert "ok" == json_response(ret_conn, 200)
564 |> post("/inbox", non_create_data)
565 |> json_response(400)
569 describe "/users/:nickname/inbox" do
572 File.read!("test/fixtures/mastodon-post-activity.json")
578 test "it inserts an incoming activity into the database", %{conn: conn, data: data} do
580 data = Map.put(data, "bcc", [user.ap_id])
584 |> assign(:valid_signature, true)
585 |> put_req_header("content-type", "application/activity+json")
586 |> post("/users/#{user.nickname}/inbox", data)
588 assert "ok" == json_response(conn, 200)
589 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
590 assert Activity.get_by_ap_id(data["id"])
593 test "it accepts messages with to as string instead of array", %{conn: conn, data: data} do
597 Map.put(data, "to", user.ap_id)
602 |> assign(:valid_signature, true)
603 |> put_req_header("content-type", "application/activity+json")
604 |> post("/users/#{user.nickname}/inbox", data)
606 assert "ok" == json_response(conn, 200)
607 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
608 assert Activity.get_by_ap_id(data["id"])
611 test "it accepts messages with cc as string instead of array", %{conn: conn, data: data} do
615 Map.put(data, "cc", user.ap_id)
620 |> assign(:valid_signature, true)
621 |> put_req_header("content-type", "application/activity+json")
622 |> post("/users/#{user.nickname}/inbox", data)
624 assert "ok" == json_response(conn, 200)
625 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
626 %Activity{} = activity = Activity.get_by_ap_id(data["id"])
627 assert user.ap_id in activity.recipients
630 test "it accepts messages with bcc as string instead of array", %{conn: conn, data: data} do
634 Map.put(data, "bcc", user.ap_id)
640 |> assign(:valid_signature, true)
641 |> put_req_header("content-type", "application/activity+json")
642 |> post("/users/#{user.nickname}/inbox", data)
644 assert "ok" == json_response(conn, 200)
645 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
646 assert Activity.get_by_ap_id(data["id"])
649 test "it accepts announces with to as string instead of array", %{conn: conn} do
652 {:ok, post} = CommonAPI.post(user, %{status: "hey"})
653 announcer = insert(:user, local: false)
656 "@context" => "https://www.w3.org/ns/activitystreams",
657 "actor" => announcer.ap_id,
658 "id" => "#{announcer.ap_id}/statuses/19512778738411822/activity",
659 "object" => post.data["object"],
660 "to" => "https://www.w3.org/ns/activitystreams#Public",
661 "cc" => [user.ap_id],
667 |> assign(:valid_signature, true)
668 |> put_req_header("content-type", "application/activity+json")
669 |> post("/users/#{user.nickname}/inbox", data)
671 assert "ok" == json_response(conn, 200)
672 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
673 %Activity{} = activity = Activity.get_by_ap_id(data["id"])
674 assert "https://www.w3.org/ns/activitystreams#Public" in activity.recipients
677 test "it accepts messages from actors that are followed by the user", %{
681 recipient = insert(:user)
682 actor = insert(:user, %{ap_id: "http://mastodon.example.org/users/actor"})
684 {:ok, recipient} = User.follow(recipient, actor)
688 |> Map.put("attributedTo", actor.ap_id)
692 |> Map.put("actor", actor.ap_id)
693 |> Map.put("object", object)
697 |> assign(:valid_signature, true)
698 |> put_req_header("content-type", "application/activity+json")
699 |> post("/users/#{recipient.nickname}/inbox", data)
701 assert "ok" == json_response(conn, 200)
702 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
703 assert Activity.get_by_ap_id(data["id"])
706 test "it rejects reads from other users", %{conn: conn} do
708 other_user = insert(:user)
712 |> assign(:user, other_user)
713 |> put_req_header("accept", "application/activity+json")
714 |> get("/users/#{user.nickname}/inbox")
716 assert json_response(conn, 403)
719 test "it returns a note activity in a collection", %{conn: conn} do
720 note_activity = insert(:direct_note_activity)
721 note_object = Object.normalize(note_activity)
722 user = User.get_cached_by_ap_id(hd(note_activity.data["to"]))
726 |> assign(:user, user)
727 |> put_req_header("accept", "application/activity+json")
728 |> get("/users/#{user.nickname}/inbox?page=true")
730 assert response(conn, 200) =~ note_object.data["content"]
733 test "it clears `unreachable` federation status of the sender", %{conn: conn, data: data} do
735 data = Map.put(data, "bcc", [user.ap_id])
737 sender_host = URI.parse(data["actor"]).host
738 Instances.set_consistently_unreachable(sender_host)
739 refute Instances.reachable?(sender_host)
743 |> assign(:valid_signature, true)
744 |> put_req_header("content-type", "application/activity+json")
745 |> post("/users/#{user.nickname}/inbox", data)
747 assert "ok" == json_response(conn, 200)
748 assert Instances.reachable?(sender_host)
751 test "it removes all follower collections but actor's", %{conn: conn} do
752 [actor, recipient] = insert_pair(:user)
755 File.read!("test/fixtures/activitypub-client-post-activity.json")
758 object = Map.put(data["object"], "attributedTo", actor.ap_id)
762 |> Map.put("id", Utils.generate_object_id())
763 |> Map.put("actor", actor.ap_id)
764 |> Map.put("object", object)
766 recipient.follower_address,
767 actor.follower_address
771 recipient.follower_address,
772 "https://www.w3.org/ns/activitystreams#Public"
776 |> assign(:valid_signature, true)
777 |> put_req_header("content-type", "application/activity+json")
778 |> post("/users/#{recipient.nickname}/inbox", data)
779 |> json_response(200)
781 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
783 activity = Activity.get_by_ap_id(data["id"])
786 assert actor.follower_address in activity.recipients
787 assert actor.follower_address in activity.data["cc"]
789 refute recipient.follower_address in activity.recipients
790 refute recipient.follower_address in activity.data["cc"]
791 refute recipient.follower_address in activity.data["to"]
794 test "it requires authentication", %{conn: conn} do
796 conn = put_req_header(conn, "accept", "application/activity+json")
798 ret_conn = get(conn, "/users/#{user.nickname}/inbox")
799 assert json_response(ret_conn, 403)
803 |> assign(:user, user)
804 |> get("/users/#{user.nickname}/inbox")
806 assert json_response(ret_conn, 200)
810 describe "GET /users/:nickname/outbox" do
811 test "it paginates correctly", %{conn: conn} do
813 conn = assign(conn, :user, user)
814 outbox_endpoint = user.ap_id <> "/outbox"
818 {:ok, activity} = CommonAPI.post(user, %{status: "post #{i}"})
824 |> put_req_header("accept", "application/activity+json")
825 |> get(outbox_endpoint <> "?page=true")
826 |> json_response(200)
828 result_ids = Enum.map(result["orderedItems"], fn x -> x["id"] end)
829 assert length(result["orderedItems"]) == 20
830 assert length(result_ids) == 20
831 assert result["next"]
832 assert String.starts_with?(result["next"], outbox_endpoint)
836 |> put_req_header("accept", "application/activity+json")
837 |> get(result["next"])
838 |> json_response(200)
840 result_next_ids = Enum.map(result_next["orderedItems"], fn x -> x["id"] end)
841 assert length(result_next["orderedItems"]) == 6
842 assert length(result_next_ids) == 6
843 refute Enum.find(result_next_ids, fn x -> x in result_ids end)
844 refute Enum.find(result_ids, fn x -> x in result_next_ids end)
845 assert String.starts_with?(result["id"], outbox_endpoint)
849 |> put_req_header("accept", "application/activity+json")
850 |> get(result_next["id"])
851 |> json_response(200)
853 assert result_next == result_next_again
856 test "it returns 200 even if there're no activities", %{conn: conn} do
858 outbox_endpoint = user.ap_id <> "/outbox"
862 |> assign(:user, user)
863 |> put_req_header("accept", "application/activity+json")
864 |> get(outbox_endpoint)
866 result = json_response(conn, 200)
867 assert outbox_endpoint == result["id"]
870 test "it returns a note activity in a collection", %{conn: conn} do
871 note_activity = insert(:note_activity)
872 note_object = Object.normalize(note_activity)
873 user = User.get_cached_by_ap_id(note_activity.data["actor"])
877 |> assign(:user, user)
878 |> put_req_header("accept", "application/activity+json")
879 |> get("/users/#{user.nickname}/outbox?page=true")
881 assert response(conn, 200) =~ note_object.data["content"]
884 test "it returns an announce activity in a collection", %{conn: conn} do
885 announce_activity = insert(:announce_activity)
886 user = User.get_cached_by_ap_id(announce_activity.data["actor"])
890 |> assign(:user, user)
891 |> put_req_header("accept", "application/activity+json")
892 |> get("/users/#{user.nickname}/outbox?page=true")
894 assert response(conn, 200) =~ announce_activity.data["object"]
897 test "it requires authentication if instance is NOT federating", %{
901 conn = put_req_header(conn, "accept", "application/activity+json")
903 ensure_federating_or_authenticated(conn, "/users/#{user.nickname}/outbox", user)
907 describe "POST /users/:nickname/outbox (C2S)" do
911 "@context" => "https://www.w3.org/ns/activitystreams",
913 "object" => %{"type" => "Note", "content" => "AP C2S test"},
914 "to" => "https://www.w3.org/ns/activitystreams#Public",
920 test "it rejects posts from other users / unauthenticated users", %{
925 other_user = insert(:user)
926 conn = put_req_header(conn, "content-type", "application/activity+json")
929 |> post("/users/#{user.nickname}/outbox", activity)
930 |> json_response(403)
933 |> assign(:user, other_user)
934 |> post("/users/#{user.nickname}/outbox", activity)
935 |> json_response(403)
938 test "it inserts an incoming create activity into the database", %{
946 |> assign(:user, user)
947 |> put_req_header("content-type", "application/activity+json")
948 |> post("/users/#{user.nickname}/outbox", activity)
949 |> json_response(201)
951 assert Activity.get_by_ap_id(result["id"])
952 assert result["object"]
953 assert %Object{data: object} = Object.normalize(result["object"])
954 assert object["content"] == activity["object"]["content"]
957 test "it rejects anything beyond 'Note' creations", %{conn: conn, activity: activity} do
962 |> put_in(["object", "type"], "Benis")
966 |> assign(:user, user)
967 |> put_req_header("content-type", "application/activity+json")
968 |> post("/users/#{user.nickname}/outbox", activity)
969 |> json_response(400)
972 test "it inserts an incoming sensitive activity into the database", %{
977 conn = assign(conn, :user, user)
978 object = Map.put(activity["object"], "sensitive", true)
979 activity = Map.put(activity, "object", object)
983 |> put_req_header("content-type", "application/activity+json")
984 |> post("/users/#{user.nickname}/outbox", activity)
985 |> json_response(201)
987 assert Activity.get_by_ap_id(response["id"])
988 assert response["object"]
989 assert %Object{data: response_object} = Object.normalize(response["object"])
990 assert response_object["sensitive"] == true
991 assert response_object["content"] == activity["object"]["content"]
995 |> put_req_header("accept", "application/activity+json")
996 |> get(response["id"])
997 |> json_response(200)
999 assert representation["object"]["sensitive"] == true
1002 test "it rejects an incoming activity with bogus type", %{conn: conn, activity: activity} do
1003 user = insert(:user)
1004 activity = Map.put(activity, "type", "BadType")
1008 |> assign(:user, user)
1009 |> put_req_header("content-type", "application/activity+json")
1010 |> post("/users/#{user.nickname}/outbox", activity)
1012 assert json_response(conn, 400)
1015 test "it erects a tombstone when receiving a delete activity", %{conn: conn} do
1016 note_activity = insert(:note_activity)
1017 note_object = Object.normalize(note_activity)
1018 user = User.get_cached_by_ap_id(note_activity.data["actor"])
1023 id: note_object.data["id"]
1029 |> assign(:user, user)
1030 |> put_req_header("content-type", "application/activity+json")
1031 |> post("/users/#{user.nickname}/outbox", data)
1033 result = json_response(conn, 201)
1034 assert Activity.get_by_ap_id(result["id"])
1036 assert object = Object.get_by_ap_id(note_object.data["id"])
1037 assert object.data["type"] == "Tombstone"
1040 test "it rejects delete activity of object from other actor", %{conn: conn} do
1041 note_activity = insert(:note_activity)
1042 note_object = Object.normalize(note_activity)
1043 user = insert(:user)
1048 id: note_object.data["id"]
1054 |> assign(:user, user)
1055 |> put_req_header("content-type", "application/activity+json")
1056 |> post("/users/#{user.nickname}/outbox", data)
1058 assert json_response(conn, 400)
1061 test "it increases like count when receiving a like action", %{conn: conn} do
1062 note_activity = insert(:note_activity)
1063 note_object = Object.normalize(note_activity)
1064 user = User.get_cached_by_ap_id(note_activity.data["actor"])
1069 id: note_object.data["id"]
1075 |> assign(:user, user)
1076 |> put_req_header("content-type", "application/activity+json")
1077 |> post("/users/#{user.nickname}/outbox", data)
1079 result = json_response(conn, 201)
1080 assert Activity.get_by_ap_id(result["id"])
1082 assert object = Object.get_by_ap_id(note_object.data["id"])
1083 assert object.data["like_count"] == 1
1087 describe "/relay/followers" do
1088 test "it returns relay followers", %{conn: conn} do
1089 relay_actor = Relay.get_actor()
1090 user = insert(:user)
1091 User.follow(user, relay_actor)
1095 |> get("/relay/followers")
1096 |> json_response(200)
1098 assert result["first"]["orderedItems"] == [user.ap_id]
1101 test "on non-federating instance, it returns 404", %{conn: conn} do
1102 Config.put([:instance, :federating], false)
1103 user = insert(:user)
1106 |> assign(:user, user)
1107 |> get("/relay/followers")
1108 |> json_response(404)
1112 describe "/relay/following" do
1113 test "it returns relay following", %{conn: conn} do
1116 |> get("/relay/following")
1117 |> json_response(200)
1119 assert result["first"]["orderedItems"] == []
1122 test "on non-federating instance, it returns 404", %{conn: conn} do
1123 Config.put([:instance, :federating], false)
1124 user = insert(:user)
1127 |> assign(:user, user)
1128 |> get("/relay/following")
1129 |> json_response(404)
1133 describe "/users/:nickname/followers" do
1134 test "it returns the followers in a collection", %{conn: conn} do
1135 user = insert(:user)
1136 user_two = insert(:user)
1137 User.follow(user, user_two)
1141 |> assign(:user, user_two)
1142 |> get("/users/#{user_two.nickname}/followers")
1143 |> json_response(200)
1145 assert result["first"]["orderedItems"] == [user.ap_id]
1148 test "it returns a uri if the user has 'hide_followers' set", %{conn: conn} do
1149 user = insert(:user)
1150 user_two = insert(:user, hide_followers: true)
1151 User.follow(user, user_two)
1155 |> assign(:user, user)
1156 |> get("/users/#{user_two.nickname}/followers")
1157 |> json_response(200)
1159 assert is_binary(result["first"])
1162 test "it returns a 403 error on pages, if the user has 'hide_followers' set and the request is from another user",
1164 user = insert(:user)
1165 other_user = insert(:user, hide_followers: true)
1169 |> assign(:user, user)
1170 |> get("/users/#{other_user.nickname}/followers?page=1")
1172 assert result.status == 403
1173 assert result.resp_body == ""
1176 test "it renders the page, if the user has 'hide_followers' set and the request is authenticated with the same user",
1178 user = insert(:user, hide_followers: true)
1179 other_user = insert(:user)
1180 {:ok, _other_user, user, _activity} = CommonAPI.follow(other_user, user)
1184 |> assign(:user, user)
1185 |> get("/users/#{user.nickname}/followers?page=1")
1186 |> json_response(200)
1188 assert result["totalItems"] == 1
1189 assert result["orderedItems"] == [other_user.ap_id]
1192 test "it works for more than 10 users", %{conn: conn} do
1193 user = insert(:user)
1195 Enum.each(1..15, fn _ ->
1196 other_user = insert(:user)
1197 User.follow(other_user, user)
1202 |> assign(:user, user)
1203 |> get("/users/#{user.nickname}/followers")
1204 |> json_response(200)
1206 assert length(result["first"]["orderedItems"]) == 10
1207 assert result["first"]["totalItems"] == 15
1208 assert result["totalItems"] == 15
1212 |> assign(:user, user)
1213 |> get("/users/#{user.nickname}/followers?page=2")
1214 |> json_response(200)
1216 assert length(result["orderedItems"]) == 5
1217 assert result["totalItems"] == 15
1220 test "does not require authentication", %{conn: conn} do
1221 user = insert(:user)
1224 |> get("/users/#{user.nickname}/followers")
1225 |> json_response(200)
1229 describe "/users/:nickname/following" do
1230 test "it returns the following in a collection", %{conn: conn} do
1231 user = insert(:user)
1232 user_two = insert(:user)
1233 User.follow(user, user_two)
1237 |> assign(:user, user)
1238 |> get("/users/#{user.nickname}/following")
1239 |> json_response(200)
1241 assert result["first"]["orderedItems"] == [user_two.ap_id]
1244 test "it returns a uri if the user has 'hide_follows' set", %{conn: conn} do
1245 user = insert(:user)
1246 user_two = insert(:user, hide_follows: true)
1247 User.follow(user, user_two)
1251 |> assign(:user, user)
1252 |> get("/users/#{user_two.nickname}/following")
1253 |> json_response(200)
1255 assert is_binary(result["first"])
1258 test "it returns a 403 error on pages, if the user has 'hide_follows' set and the request is from another user",
1260 user = insert(:user)
1261 user_two = insert(:user, hide_follows: true)
1265 |> assign(:user, user)
1266 |> get("/users/#{user_two.nickname}/following?page=1")
1268 assert result.status == 403
1269 assert result.resp_body == ""
1272 test "it renders the page, if the user has 'hide_follows' set and the request is authenticated with the same user",
1274 user = insert(:user, hide_follows: true)
1275 other_user = insert(:user)
1276 {:ok, user, _other_user, _activity} = CommonAPI.follow(user, other_user)
1280 |> assign(:user, user)
1281 |> get("/users/#{user.nickname}/following?page=1")
1282 |> json_response(200)
1284 assert result["totalItems"] == 1
1285 assert result["orderedItems"] == [other_user.ap_id]
1288 test "it works for more than 10 users", %{conn: conn} do
1289 user = insert(:user)
1291 Enum.each(1..15, fn _ ->
1292 user = User.get_cached_by_id(user.id)
1293 other_user = insert(:user)
1294 User.follow(user, other_user)
1299 |> assign(:user, user)
1300 |> get("/users/#{user.nickname}/following")
1301 |> json_response(200)
1303 assert length(result["first"]["orderedItems"]) == 10
1304 assert result["first"]["totalItems"] == 15
1305 assert result["totalItems"] == 15
1309 |> assign(:user, user)
1310 |> get("/users/#{user.nickname}/following?page=2")
1311 |> json_response(200)
1313 assert length(result["orderedItems"]) == 5
1314 assert result["totalItems"] == 15
1317 test "does not require authentication", %{conn: conn} do
1318 user = insert(:user)
1321 |> get("/users/#{user.nickname}/following")
1322 |> json_response(200)
1326 describe "delivery tracking" do
1327 test "it tracks a signed object fetch", %{conn: conn} do
1328 user = insert(:user, local: false)
1329 activity = insert(:note_activity)
1330 object = Object.normalize(activity)
1332 object_path = String.trim_leading(object.data["id"], Pleroma.Web.Endpoint.url())
1335 |> put_req_header("accept", "application/activity+json")
1336 |> assign(:user, user)
1338 |> json_response(200)
1340 assert Delivery.get(object.id, user.id)
1343 test "it tracks a signed activity fetch", %{conn: conn} do
1344 user = insert(:user, local: false)
1345 activity = insert(:note_activity)
1346 object = Object.normalize(activity)
1348 activity_path = String.trim_leading(activity.data["id"], Pleroma.Web.Endpoint.url())
1351 |> put_req_header("accept", "application/activity+json")
1352 |> assign(:user, user)
1353 |> get(activity_path)
1354 |> json_response(200)
1356 assert Delivery.get(object.id, user.id)
1359 test "it tracks a signed object fetch when the json is cached", %{conn: conn} do
1360 user = insert(:user, local: false)
1361 other_user = insert(:user, local: false)
1362 activity = insert(:note_activity)
1363 object = Object.normalize(activity)
1365 object_path = String.trim_leading(object.data["id"], Pleroma.Web.Endpoint.url())
1368 |> put_req_header("accept", "application/activity+json")
1369 |> assign(:user, user)
1371 |> json_response(200)
1374 |> put_req_header("accept", "application/activity+json")
1375 |> assign(:user, other_user)
1377 |> json_response(200)
1379 assert Delivery.get(object.id, user.id)
1380 assert Delivery.get(object.id, other_user.id)
1383 test "it tracks a signed activity fetch when the json is cached", %{conn: conn} do
1384 user = insert(:user, local: false)
1385 other_user = insert(:user, local: false)
1386 activity = insert(:note_activity)
1387 object = Object.normalize(activity)
1389 activity_path = String.trim_leading(activity.data["id"], Pleroma.Web.Endpoint.url())
1392 |> put_req_header("accept", "application/activity+json")
1393 |> assign(:user, user)
1394 |> get(activity_path)
1395 |> json_response(200)
1398 |> put_req_header("accept", "application/activity+json")
1399 |> assign(:user, other_user)
1400 |> get(activity_path)
1401 |> json_response(200)
1403 assert Delivery.get(object.id, user.id)
1404 assert Delivery.get(object.id, other_user.id)
1408 describe "Additional ActivityPub C2S endpoints" do
1409 test "GET /api/ap/whoami", %{conn: conn} do
1410 user = insert(:user)
1414 |> assign(:user, user)
1415 |> get("/api/ap/whoami")
1417 user = User.get_cached_by_id(user.id)
1419 assert UserView.render("user.json", %{user: user}) == json_response(conn, 200)
1422 |> get("/api/ap/whoami")
1423 |> json_response(403)
1426 setup do: clear_config([:media_proxy])
1427 setup do: clear_config([Pleroma.Upload])
1429 test "POST /api/ap/upload_media", %{conn: conn} do
1430 user = insert(:user)
1432 desc = "Description of the image"
1434 image = %Plug.Upload{
1435 content_type: "image/jpg",
1436 path: Path.absname("test/fixtures/image.jpg"),
1437 filename: "an_image.jpg"
1442 |> assign(:user, user)
1443 |> post("/api/ap/upload_media", %{"file" => image, "description" => desc})
1444 |> json_response(:created)
1446 assert object["name"] == desc
1447 assert object["type"] == "Document"
1448 assert object["actor"] == user.ap_id
1449 assert [%{"href" => object_href, "mediaType" => object_mediatype}] = object["url"]
1450 assert is_binary(object_href)
1451 assert object_mediatype == "image/jpeg"
1453 activity_request = %{
1454 "@context" => "https://www.w3.org/ns/activitystreams",
1458 "content" => "AP C2S test, attachment",
1459 "attachment" => [object]
1461 "to" => "https://www.w3.org/ns/activitystreams#Public",
1467 |> assign(:user, user)
1468 |> post("/users/#{user.nickname}/outbox", activity_request)
1469 |> json_response(:created)
1471 assert activity_response["id"]
1472 assert activity_response["object"]
1473 assert activity_response["actor"] == user.ap_id
1475 assert %Object{data: %{"attachment" => [attachment]}} =
1476 Object.normalize(activity_response["object"])
1478 assert attachment["type"] == "Document"
1479 assert attachment["name"] == desc
1483 "href" => ^object_href,
1485 "mediaType" => ^object_mediatype
1487 ] = attachment["url"]
1489 # Fails if unauthenticated
1491 |> post("/api/ap/upload_media", %{"file" => image, "description" => desc})
1492 |> json_response(403)