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
1086 test "it doesn't spreads faulty attributedTo or actor fields", %{
1090 reimu = insert(:user, nickname: "reimu")
1091 cirno = insert(:user, nickname: "cirno")
1098 |> put_in(["object", "actor"], reimu.ap_id)
1099 |> put_in(["object", "attributedTo"], reimu.ap_id)
1100 |> put_in(["actor"], reimu.ap_id)
1101 |> put_in(["attributedTo"], reimu.ap_id)
1105 |> assign(:user, cirno)
1106 |> put_req_header("content-type", "application/activity+json")
1107 |> post("/users/#{reimu.nickname}/outbox", activity)
1108 |> json_response(403)
1112 |> assign(:user, cirno)
1113 |> put_req_header("content-type", "application/activity+json")
1114 |> post("/users/#{cirno.nickname}/outbox", activity)
1115 |> json_response(201)
1117 assert cirno_outbox["attributedTo"] == nil
1118 assert cirno_outbox["actor"] == cirno.ap_id
1120 assert cirno_object = Object.normalize(cirno_outbox["object"])
1121 assert cirno_object.data["actor"] == cirno.ap_id
1122 assert cirno_object.data["attributedTo"] == cirno.ap_id
1126 describe "/relay/followers" do
1127 test "it returns relay followers", %{conn: conn} do
1128 relay_actor = Relay.get_actor()
1129 user = insert(:user)
1130 User.follow(user, relay_actor)
1134 |> get("/relay/followers")
1135 |> json_response(200)
1137 assert result["first"]["orderedItems"] == [user.ap_id]
1140 test "on non-federating instance, it returns 404", %{conn: conn} do
1141 Config.put([:instance, :federating], false)
1142 user = insert(:user)
1145 |> assign(:user, user)
1146 |> get("/relay/followers")
1147 |> json_response(404)
1151 describe "/relay/following" do
1152 test "it returns relay following", %{conn: conn} do
1155 |> get("/relay/following")
1156 |> json_response(200)
1158 assert result["first"]["orderedItems"] == []
1161 test "on non-federating instance, it returns 404", %{conn: conn} do
1162 Config.put([:instance, :federating], false)
1163 user = insert(:user)
1166 |> assign(:user, user)
1167 |> get("/relay/following")
1168 |> json_response(404)
1172 describe "/users/:nickname/followers" do
1173 test "it returns the followers in a collection", %{conn: conn} do
1174 user = insert(:user)
1175 user_two = insert(:user)
1176 User.follow(user, user_two)
1180 |> assign(:user, user_two)
1181 |> get("/users/#{user_two.nickname}/followers")
1182 |> json_response(200)
1184 assert result["first"]["orderedItems"] == [user.ap_id]
1187 test "it returns a uri if the user has 'hide_followers' set", %{conn: conn} do
1188 user = insert(:user)
1189 user_two = insert(:user, hide_followers: true)
1190 User.follow(user, user_two)
1194 |> assign(:user, user)
1195 |> get("/users/#{user_two.nickname}/followers")
1196 |> json_response(200)
1198 assert is_binary(result["first"])
1201 test "it returns a 403 error on pages, if the user has 'hide_followers' set and the request is from another user",
1203 user = insert(:user)
1204 other_user = insert(:user, hide_followers: true)
1208 |> assign(:user, user)
1209 |> get("/users/#{other_user.nickname}/followers?page=1")
1211 assert result.status == 403
1212 assert result.resp_body == ""
1215 test "it renders the page, if the user has 'hide_followers' set and the request is authenticated with the same user",
1217 user = insert(:user, hide_followers: true)
1218 other_user = insert(:user)
1219 {:ok, _other_user, user, _activity} = CommonAPI.follow(other_user, user)
1223 |> assign(:user, user)
1224 |> get("/users/#{user.nickname}/followers?page=1")
1225 |> json_response(200)
1227 assert result["totalItems"] == 1
1228 assert result["orderedItems"] == [other_user.ap_id]
1231 test "it works for more than 10 users", %{conn: conn} do
1232 user = insert(:user)
1234 Enum.each(1..15, fn _ ->
1235 other_user = insert(:user)
1236 User.follow(other_user, user)
1241 |> assign(:user, user)
1242 |> get("/users/#{user.nickname}/followers")
1243 |> json_response(200)
1245 assert length(result["first"]["orderedItems"]) == 10
1246 assert result["first"]["totalItems"] == 15
1247 assert result["totalItems"] == 15
1251 |> assign(:user, user)
1252 |> get("/users/#{user.nickname}/followers?page=2")
1253 |> json_response(200)
1255 assert length(result["orderedItems"]) == 5
1256 assert result["totalItems"] == 15
1259 test "does not require authentication", %{conn: conn} do
1260 user = insert(:user)
1263 |> get("/users/#{user.nickname}/followers")
1264 |> json_response(200)
1268 describe "/users/:nickname/following" do
1269 test "it returns the following in a collection", %{conn: conn} do
1270 user = insert(:user)
1271 user_two = insert(:user)
1272 User.follow(user, user_two)
1276 |> assign(:user, user)
1277 |> get("/users/#{user.nickname}/following")
1278 |> json_response(200)
1280 assert result["first"]["orderedItems"] == [user_two.ap_id]
1283 test "it returns a uri if the user has 'hide_follows' set", %{conn: conn} do
1284 user = insert(:user)
1285 user_two = insert(:user, hide_follows: true)
1286 User.follow(user, user_two)
1290 |> assign(:user, user)
1291 |> get("/users/#{user_two.nickname}/following")
1292 |> json_response(200)
1294 assert is_binary(result["first"])
1297 test "it returns a 403 error on pages, if the user has 'hide_follows' set and the request is from another user",
1299 user = insert(:user)
1300 user_two = insert(:user, hide_follows: true)
1304 |> assign(:user, user)
1305 |> get("/users/#{user_two.nickname}/following?page=1")
1307 assert result.status == 403
1308 assert result.resp_body == ""
1311 test "it renders the page, if the user has 'hide_follows' set and the request is authenticated with the same user",
1313 user = insert(:user, hide_follows: true)
1314 other_user = insert(:user)
1315 {:ok, user, _other_user, _activity} = CommonAPI.follow(user, other_user)
1319 |> assign(:user, user)
1320 |> get("/users/#{user.nickname}/following?page=1")
1321 |> json_response(200)
1323 assert result["totalItems"] == 1
1324 assert result["orderedItems"] == [other_user.ap_id]
1327 test "it works for more than 10 users", %{conn: conn} do
1328 user = insert(:user)
1330 Enum.each(1..15, fn _ ->
1331 user = User.get_cached_by_id(user.id)
1332 other_user = insert(:user)
1333 User.follow(user, other_user)
1338 |> assign(:user, user)
1339 |> get("/users/#{user.nickname}/following")
1340 |> json_response(200)
1342 assert length(result["first"]["orderedItems"]) == 10
1343 assert result["first"]["totalItems"] == 15
1344 assert result["totalItems"] == 15
1348 |> assign(:user, user)
1349 |> get("/users/#{user.nickname}/following?page=2")
1350 |> json_response(200)
1352 assert length(result["orderedItems"]) == 5
1353 assert result["totalItems"] == 15
1356 test "does not require authentication", %{conn: conn} do
1357 user = insert(:user)
1360 |> get("/users/#{user.nickname}/following")
1361 |> json_response(200)
1365 describe "delivery tracking" do
1366 test "it tracks a signed object fetch", %{conn: conn} do
1367 user = insert(:user, local: false)
1368 activity = insert(:note_activity)
1369 object = Object.normalize(activity)
1371 object_path = String.trim_leading(object.data["id"], Pleroma.Web.Endpoint.url())
1374 |> put_req_header("accept", "application/activity+json")
1375 |> assign(:user, user)
1377 |> json_response(200)
1379 assert Delivery.get(object.id, user.id)
1382 test "it tracks a signed activity fetch", %{conn: conn} do
1383 user = insert(:user, local: false)
1384 activity = insert(:note_activity)
1385 object = Object.normalize(activity)
1387 activity_path = String.trim_leading(activity.data["id"], Pleroma.Web.Endpoint.url())
1390 |> put_req_header("accept", "application/activity+json")
1391 |> assign(:user, user)
1392 |> get(activity_path)
1393 |> json_response(200)
1395 assert Delivery.get(object.id, user.id)
1398 test "it tracks a signed object fetch when the json is cached", %{conn: conn} do
1399 user = insert(:user, local: false)
1400 other_user = insert(:user, local: false)
1401 activity = insert(:note_activity)
1402 object = Object.normalize(activity)
1404 object_path = String.trim_leading(object.data["id"], Pleroma.Web.Endpoint.url())
1407 |> put_req_header("accept", "application/activity+json")
1408 |> assign(:user, user)
1410 |> json_response(200)
1413 |> put_req_header("accept", "application/activity+json")
1414 |> assign(:user, other_user)
1416 |> json_response(200)
1418 assert Delivery.get(object.id, user.id)
1419 assert Delivery.get(object.id, other_user.id)
1422 test "it tracks a signed activity fetch when the json is cached", %{conn: conn} do
1423 user = insert(:user, local: false)
1424 other_user = insert(:user, local: false)
1425 activity = insert(:note_activity)
1426 object = Object.normalize(activity)
1428 activity_path = String.trim_leading(activity.data["id"], Pleroma.Web.Endpoint.url())
1431 |> put_req_header("accept", "application/activity+json")
1432 |> assign(:user, user)
1433 |> get(activity_path)
1434 |> json_response(200)
1437 |> put_req_header("accept", "application/activity+json")
1438 |> assign(:user, other_user)
1439 |> get(activity_path)
1440 |> json_response(200)
1442 assert Delivery.get(object.id, user.id)
1443 assert Delivery.get(object.id, other_user.id)
1447 describe "Additional ActivityPub C2S endpoints" do
1448 test "GET /api/ap/whoami", %{conn: conn} do
1449 user = insert(:user)
1453 |> assign(:user, user)
1454 |> get("/api/ap/whoami")
1456 user = User.get_cached_by_id(user.id)
1458 assert UserView.render("user.json", %{user: user}) == json_response(conn, 200)
1461 |> get("/api/ap/whoami")
1462 |> json_response(403)
1465 setup do: clear_config([:media_proxy])
1466 setup do: clear_config([Pleroma.Upload])
1468 test "POST /api/ap/upload_media", %{conn: conn} do
1469 user = insert(:user)
1471 desc = "Description of the image"
1473 image = %Plug.Upload{
1474 content_type: "image/jpg",
1475 path: Path.absname("test/fixtures/image.jpg"),
1476 filename: "an_image.jpg"
1481 |> assign(:user, user)
1482 |> post("/api/ap/upload_media", %{"file" => image, "description" => desc})
1483 |> json_response(:created)
1485 assert object["name"] == desc
1486 assert object["type"] == "Document"
1487 assert object["actor"] == user.ap_id
1488 assert [%{"href" => object_href, "mediaType" => object_mediatype}] = object["url"]
1489 assert is_binary(object_href)
1490 assert object_mediatype == "image/jpeg"
1492 activity_request = %{
1493 "@context" => "https://www.w3.org/ns/activitystreams",
1497 "content" => "AP C2S test, attachment",
1498 "attachment" => [object]
1500 "to" => "https://www.w3.org/ns/activitystreams#Public",
1506 |> assign(:user, user)
1507 |> post("/users/#{user.nickname}/outbox", activity_request)
1508 |> json_response(:created)
1510 assert activity_response["id"]
1511 assert activity_response["object"]
1512 assert activity_response["actor"] == user.ap_id
1514 assert %Object{data: %{"attachment" => [attachment]}} =
1515 Object.normalize(activity_response["object"])
1517 assert attachment["type"] == "Document"
1518 assert attachment["name"] == desc
1522 "href" => ^object_href,
1524 "mediaType" => ^object_mediatype
1526 ] = attachment["url"]
1528 # Fails if unauthenticated
1530 |> post("/api/ap/upload_media", %{"file" => image, "description" => desc})
1531 |> json_response(403)