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 test "without valid signature, " <>
540 "it only accepts Create activities and requires enabled federation",
542 data = File.read!("test/fixtures/mastodon-post-activity.json") |> Poison.decode!()
543 non_create_data = File.read!("test/fixtures/mastodon-announce.json") |> Poison.decode!()
545 conn = put_req_header(conn, "content-type", "application/activity+json")
547 Config.put([:instance, :federating], false)
550 |> post("/inbox", data)
551 |> json_response(403)
554 |> post("/inbox", non_create_data)
555 |> json_response(403)
557 Config.put([:instance, :federating], true)
559 ret_conn = post(conn, "/inbox", data)
560 assert "ok" == json_response(ret_conn, 200)
563 |> post("/inbox", non_create_data)
564 |> json_response(400)
568 describe "/users/:nickname/inbox" do
571 File.read!("test/fixtures/mastodon-post-activity.json")
577 test "it inserts an incoming activity into the database", %{conn: conn, data: data} do
579 data = Map.put(data, "bcc", [user.ap_id])
583 |> assign(:valid_signature, true)
584 |> put_req_header("content-type", "application/activity+json")
585 |> post("/users/#{user.nickname}/inbox", data)
587 assert "ok" == json_response(conn, 200)
588 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
589 assert Activity.get_by_ap_id(data["id"])
592 test "it accepts messages with to as string instead of array", %{conn: conn, data: data} do
596 Map.put(data, "to", user.ap_id)
601 |> assign(:valid_signature, true)
602 |> put_req_header("content-type", "application/activity+json")
603 |> post("/users/#{user.nickname}/inbox", data)
605 assert "ok" == json_response(conn, 200)
606 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
607 assert Activity.get_by_ap_id(data["id"])
610 test "it accepts messages with cc as string instead of array", %{conn: conn, data: data} do
614 Map.put(data, "cc", user.ap_id)
619 |> assign(:valid_signature, true)
620 |> put_req_header("content-type", "application/activity+json")
621 |> post("/users/#{user.nickname}/inbox", data)
623 assert "ok" == json_response(conn, 200)
624 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
625 %Activity{} = activity = Activity.get_by_ap_id(data["id"])
626 assert user.ap_id in activity.recipients
629 test "it accepts messages with bcc as string instead of array", %{conn: conn, data: data} do
633 Map.put(data, "bcc", user.ap_id)
639 |> assign(:valid_signature, true)
640 |> put_req_header("content-type", "application/activity+json")
641 |> post("/users/#{user.nickname}/inbox", data)
643 assert "ok" == json_response(conn, 200)
644 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
645 assert Activity.get_by_ap_id(data["id"])
648 test "it accepts announces with to as string instead of array", %{conn: conn} do
652 "@context" => "https://www.w3.org/ns/activitystreams",
653 "actor" => "http://mastodon.example.org/users/admin",
654 "id" => "http://mastodon.example.org/users/admin/statuses/19512778738411822/activity",
655 "object" => "https://mastodon.social/users/emelie/statuses/101849165031453009",
656 "to" => "https://www.w3.org/ns/activitystreams#Public",
657 "cc" => [user.ap_id],
663 |> assign(:valid_signature, true)
664 |> put_req_header("content-type", "application/activity+json")
665 |> post("/users/#{user.nickname}/inbox", data)
667 assert "ok" == json_response(conn, 200)
668 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
669 %Activity{} = activity = Activity.get_by_ap_id(data["id"])
670 assert "https://www.w3.org/ns/activitystreams#Public" in activity.recipients
673 test "it accepts messages from actors that are followed by the user", %{
677 recipient = insert(:user)
678 actor = insert(:user, %{ap_id: "http://mastodon.example.org/users/actor"})
680 {:ok, recipient} = User.follow(recipient, actor)
684 |> Map.put("attributedTo", actor.ap_id)
688 |> Map.put("actor", actor.ap_id)
689 |> Map.put("object", object)
693 |> assign(:valid_signature, true)
694 |> put_req_header("content-type", "application/activity+json")
695 |> post("/users/#{recipient.nickname}/inbox", data)
697 assert "ok" == json_response(conn, 200)
698 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
699 assert Activity.get_by_ap_id(data["id"])
702 test "it rejects reads from other users", %{conn: conn} do
704 other_user = insert(:user)
708 |> assign(:user, other_user)
709 |> put_req_header("accept", "application/activity+json")
710 |> get("/users/#{user.nickname}/inbox")
712 assert json_response(conn, 403)
715 test "it returns a note activity in a collection", %{conn: conn} do
716 note_activity = insert(:direct_note_activity)
717 note_object = Object.normalize(note_activity)
718 user = User.get_cached_by_ap_id(hd(note_activity.data["to"]))
722 |> assign(:user, user)
723 |> put_req_header("accept", "application/activity+json")
724 |> get("/users/#{user.nickname}/inbox?page=true")
726 assert response(conn, 200) =~ note_object.data["content"]
729 test "it clears `unreachable` federation status of the sender", %{conn: conn, data: data} do
731 data = Map.put(data, "bcc", [user.ap_id])
733 sender_host = URI.parse(data["actor"]).host
734 Instances.set_consistently_unreachable(sender_host)
735 refute Instances.reachable?(sender_host)
739 |> assign(:valid_signature, true)
740 |> put_req_header("content-type", "application/activity+json")
741 |> post("/users/#{user.nickname}/inbox", data)
743 assert "ok" == json_response(conn, 200)
744 assert Instances.reachable?(sender_host)
747 test "it removes all follower collections but actor's", %{conn: conn} do
748 [actor, recipient] = insert_pair(:user)
751 File.read!("test/fixtures/activitypub-client-post-activity.json")
754 object = Map.put(data["object"], "attributedTo", actor.ap_id)
758 |> Map.put("id", Utils.generate_object_id())
759 |> Map.put("actor", actor.ap_id)
760 |> Map.put("object", object)
762 recipient.follower_address,
763 actor.follower_address
767 recipient.follower_address,
768 "https://www.w3.org/ns/activitystreams#Public"
772 |> assign(:valid_signature, true)
773 |> put_req_header("content-type", "application/activity+json")
774 |> post("/users/#{recipient.nickname}/inbox", data)
775 |> json_response(200)
777 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
779 activity = Activity.get_by_ap_id(data["id"])
782 assert actor.follower_address in activity.recipients
783 assert actor.follower_address in activity.data["cc"]
785 refute recipient.follower_address in activity.recipients
786 refute recipient.follower_address in activity.data["cc"]
787 refute recipient.follower_address in activity.data["to"]
790 test "it requires authentication", %{conn: conn} do
792 conn = put_req_header(conn, "accept", "application/activity+json")
794 ret_conn = get(conn, "/users/#{user.nickname}/inbox")
795 assert json_response(ret_conn, 403)
799 |> assign(:user, user)
800 |> get("/users/#{user.nickname}/inbox")
802 assert json_response(ret_conn, 200)
806 describe "GET /users/:nickname/outbox" do
807 test "it paginates correctly", %{conn: conn} do
809 conn = assign(conn, :user, user)
810 outbox_endpoint = user.ap_id <> "/outbox"
814 {:ok, activity} = CommonAPI.post(user, %{status: "post #{i}"})
820 |> put_req_header("accept", "application/activity+json")
821 |> get(outbox_endpoint <> "?page=true")
822 |> json_response(200)
824 result_ids = Enum.map(result["orderedItems"], fn x -> x["id"] end)
825 assert length(result["orderedItems"]) == 20
826 assert length(result_ids) == 20
827 assert result["next"]
828 assert String.starts_with?(result["next"], outbox_endpoint)
832 |> put_req_header("accept", "application/activity+json")
833 |> get(result["next"])
834 |> json_response(200)
836 result_next_ids = Enum.map(result_next["orderedItems"], fn x -> x["id"] end)
837 assert length(result_next["orderedItems"]) == 6
838 assert length(result_next_ids) == 6
839 refute Enum.find(result_next_ids, fn x -> x in result_ids end)
840 refute Enum.find(result_ids, fn x -> x in result_next_ids end)
841 assert String.starts_with?(result["id"], outbox_endpoint)
845 |> put_req_header("accept", "application/activity+json")
846 |> get(result_next["id"])
847 |> json_response(200)
849 assert result_next == result_next_again
852 test "it returns 200 even if there're no activities", %{conn: conn} do
854 outbox_endpoint = user.ap_id <> "/outbox"
858 |> assign(:user, user)
859 |> put_req_header("accept", "application/activity+json")
860 |> get(outbox_endpoint)
862 result = json_response(conn, 200)
863 assert outbox_endpoint == result["id"]
866 test "it returns a note activity in a collection", %{conn: conn} do
867 note_activity = insert(:note_activity)
868 note_object = Object.normalize(note_activity)
869 user = User.get_cached_by_ap_id(note_activity.data["actor"])
873 |> assign(:user, user)
874 |> put_req_header("accept", "application/activity+json")
875 |> get("/users/#{user.nickname}/outbox?page=true")
877 assert response(conn, 200) =~ note_object.data["content"]
880 test "it returns an announce activity in a collection", %{conn: conn} do
881 announce_activity = insert(:announce_activity)
882 user = User.get_cached_by_ap_id(announce_activity.data["actor"])
886 |> assign(:user, user)
887 |> put_req_header("accept", "application/activity+json")
888 |> get("/users/#{user.nickname}/outbox?page=true")
890 assert response(conn, 200) =~ announce_activity.data["object"]
893 test "it requires authentication if instance is NOT federating", %{
897 conn = put_req_header(conn, "accept", "application/activity+json")
899 ensure_federating_or_authenticated(conn, "/users/#{user.nickname}/outbox", user)
903 describe "POST /users/:nickname/outbox (C2S)" do
907 "@context" => "https://www.w3.org/ns/activitystreams",
909 "object" => %{"type" => "Note", "content" => "AP C2S test"},
910 "to" => "https://www.w3.org/ns/activitystreams#Public",
916 test "it rejects posts from other users / unauthenticated users", %{
921 other_user = insert(:user)
922 conn = put_req_header(conn, "content-type", "application/activity+json")
925 |> post("/users/#{user.nickname}/outbox", activity)
926 |> json_response(403)
929 |> assign(:user, other_user)
930 |> post("/users/#{user.nickname}/outbox", activity)
931 |> json_response(403)
934 test "it inserts an incoming create activity into the database", %{
942 |> assign(:user, user)
943 |> put_req_header("content-type", "application/activity+json")
944 |> post("/users/#{user.nickname}/outbox", activity)
945 |> json_response(201)
947 assert Activity.get_by_ap_id(result["id"])
948 assert result["object"]
949 assert %Object{data: object} = Object.normalize(result["object"])
950 assert object["content"] == activity["object"]["content"]
953 test "it rejects anything beyond 'Note' creations", %{conn: conn, activity: activity} do
958 |> put_in(["object", "type"], "Benis")
962 |> assign(:user, user)
963 |> put_req_header("content-type", "application/activity+json")
964 |> post("/users/#{user.nickname}/outbox", activity)
965 |> json_response(400)
968 test "it inserts an incoming sensitive activity into the database", %{
973 conn = assign(conn, :user, user)
974 object = Map.put(activity["object"], "sensitive", true)
975 activity = Map.put(activity, "object", object)
979 |> put_req_header("content-type", "application/activity+json")
980 |> post("/users/#{user.nickname}/outbox", activity)
981 |> json_response(201)
983 assert Activity.get_by_ap_id(response["id"])
984 assert response["object"]
985 assert %Object{data: response_object} = Object.normalize(response["object"])
986 assert response_object["sensitive"] == true
987 assert response_object["content"] == activity["object"]["content"]
991 |> put_req_header("accept", "application/activity+json")
992 |> get(response["id"])
993 |> json_response(200)
995 assert representation["object"]["sensitive"] == true
998 test "it rejects an incoming activity with bogus type", %{conn: conn, activity: activity} do
1000 activity = Map.put(activity, "type", "BadType")
1004 |> assign(:user, user)
1005 |> put_req_header("content-type", "application/activity+json")
1006 |> post("/users/#{user.nickname}/outbox", activity)
1008 assert json_response(conn, 400)
1011 test "it erects a tombstone when receiving a delete activity", %{conn: conn} do
1012 note_activity = insert(:note_activity)
1013 note_object = Object.normalize(note_activity)
1014 user = User.get_cached_by_ap_id(note_activity.data["actor"])
1019 id: note_object.data["id"]
1025 |> assign(:user, user)
1026 |> put_req_header("content-type", "application/activity+json")
1027 |> post("/users/#{user.nickname}/outbox", data)
1029 result = json_response(conn, 201)
1030 assert Activity.get_by_ap_id(result["id"])
1032 assert object = Object.get_by_ap_id(note_object.data["id"])
1033 assert object.data["type"] == "Tombstone"
1036 test "it rejects delete activity of object from other actor", %{conn: conn} do
1037 note_activity = insert(:note_activity)
1038 note_object = Object.normalize(note_activity)
1039 user = insert(:user)
1044 id: note_object.data["id"]
1050 |> assign(:user, user)
1051 |> put_req_header("content-type", "application/activity+json")
1052 |> post("/users/#{user.nickname}/outbox", data)
1054 assert json_response(conn, 400)
1057 test "it increases like count when receiving a like action", %{conn: conn} do
1058 note_activity = insert(:note_activity)
1059 note_object = Object.normalize(note_activity)
1060 user = User.get_cached_by_ap_id(note_activity.data["actor"])
1065 id: note_object.data["id"]
1071 |> assign(:user, user)
1072 |> put_req_header("content-type", "application/activity+json")
1073 |> post("/users/#{user.nickname}/outbox", data)
1075 result = json_response(conn, 201)
1076 assert Activity.get_by_ap_id(result["id"])
1078 assert object = Object.get_by_ap_id(note_object.data["id"])
1079 assert object.data["like_count"] == 1
1083 describe "/relay/followers" do
1084 test "it returns relay followers", %{conn: conn} do
1085 relay_actor = Relay.get_actor()
1086 user = insert(:user)
1087 User.follow(user, relay_actor)
1091 |> get("/relay/followers")
1092 |> json_response(200)
1094 assert result["first"]["orderedItems"] == [user.ap_id]
1097 test "on non-federating instance, it returns 404", %{conn: conn} do
1098 Config.put([:instance, :federating], false)
1099 user = insert(:user)
1102 |> assign(:user, user)
1103 |> get("/relay/followers")
1104 |> json_response(404)
1108 describe "/relay/following" do
1109 test "it returns relay following", %{conn: conn} do
1112 |> get("/relay/following")
1113 |> json_response(200)
1115 assert result["first"]["orderedItems"] == []
1118 test "on non-federating instance, it returns 404", %{conn: conn} do
1119 Config.put([:instance, :federating], false)
1120 user = insert(:user)
1123 |> assign(:user, user)
1124 |> get("/relay/following")
1125 |> json_response(404)
1129 describe "/users/:nickname/followers" do
1130 test "it returns the followers in a collection", %{conn: conn} do
1131 user = insert(:user)
1132 user_two = insert(:user)
1133 User.follow(user, user_two)
1137 |> assign(:user, user_two)
1138 |> get("/users/#{user_two.nickname}/followers")
1139 |> json_response(200)
1141 assert result["first"]["orderedItems"] == [user.ap_id]
1144 test "it returns a uri if the user has 'hide_followers' set", %{conn: conn} do
1145 user = insert(:user)
1146 user_two = insert(:user, hide_followers: true)
1147 User.follow(user, user_two)
1151 |> assign(:user, user)
1152 |> get("/users/#{user_two.nickname}/followers")
1153 |> json_response(200)
1155 assert is_binary(result["first"])
1158 test "it returns a 403 error on pages, if the user has 'hide_followers' set and the request is from another user",
1160 user = insert(:user)
1161 other_user = insert(:user, hide_followers: true)
1165 |> assign(:user, user)
1166 |> get("/users/#{other_user.nickname}/followers?page=1")
1168 assert result.status == 403
1169 assert result.resp_body == ""
1172 test "it renders the page, if the user has 'hide_followers' set and the request is authenticated with the same user",
1174 user = insert(:user, hide_followers: true)
1175 other_user = insert(:user)
1176 {:ok, _other_user, user, _activity} = CommonAPI.follow(other_user, user)
1180 |> assign(:user, user)
1181 |> get("/users/#{user.nickname}/followers?page=1")
1182 |> json_response(200)
1184 assert result["totalItems"] == 1
1185 assert result["orderedItems"] == [other_user.ap_id]
1188 test "it works for more than 10 users", %{conn: conn} do
1189 user = insert(:user)
1191 Enum.each(1..15, fn _ ->
1192 other_user = insert(:user)
1193 User.follow(other_user, user)
1198 |> assign(:user, user)
1199 |> get("/users/#{user.nickname}/followers")
1200 |> json_response(200)
1202 assert length(result["first"]["orderedItems"]) == 10
1203 assert result["first"]["totalItems"] == 15
1204 assert result["totalItems"] == 15
1208 |> assign(:user, user)
1209 |> get("/users/#{user.nickname}/followers?page=2")
1210 |> json_response(200)
1212 assert length(result["orderedItems"]) == 5
1213 assert result["totalItems"] == 15
1216 test "does not require authentication", %{conn: conn} do
1217 user = insert(:user)
1220 |> get("/users/#{user.nickname}/followers")
1221 |> json_response(200)
1225 describe "/users/:nickname/following" do
1226 test "it returns the following in a collection", %{conn: conn} do
1227 user = insert(:user)
1228 user_two = insert(:user)
1229 User.follow(user, user_two)
1233 |> assign(:user, user)
1234 |> get("/users/#{user.nickname}/following")
1235 |> json_response(200)
1237 assert result["first"]["orderedItems"] == [user_two.ap_id]
1240 test "it returns a uri if the user has 'hide_follows' set", %{conn: conn} do
1241 user = insert(:user)
1242 user_two = insert(:user, hide_follows: true)
1243 User.follow(user, user_two)
1247 |> assign(:user, user)
1248 |> get("/users/#{user_two.nickname}/following")
1249 |> json_response(200)
1251 assert is_binary(result["first"])
1254 test "it returns a 403 error on pages, if the user has 'hide_follows' set and the request is from another user",
1256 user = insert(:user)
1257 user_two = insert(:user, hide_follows: true)
1261 |> assign(:user, user)
1262 |> get("/users/#{user_two.nickname}/following?page=1")
1264 assert result.status == 403
1265 assert result.resp_body == ""
1268 test "it renders the page, if the user has 'hide_follows' set and the request is authenticated with the same user",
1270 user = insert(:user, hide_follows: true)
1271 other_user = insert(:user)
1272 {:ok, user, _other_user, _activity} = CommonAPI.follow(user, other_user)
1276 |> assign(:user, user)
1277 |> get("/users/#{user.nickname}/following?page=1")
1278 |> json_response(200)
1280 assert result["totalItems"] == 1
1281 assert result["orderedItems"] == [other_user.ap_id]
1284 test "it works for more than 10 users", %{conn: conn} do
1285 user = insert(:user)
1287 Enum.each(1..15, fn _ ->
1288 user = User.get_cached_by_id(user.id)
1289 other_user = insert(:user)
1290 User.follow(user, other_user)
1295 |> assign(:user, user)
1296 |> get("/users/#{user.nickname}/following")
1297 |> json_response(200)
1299 assert length(result["first"]["orderedItems"]) == 10
1300 assert result["first"]["totalItems"] == 15
1301 assert result["totalItems"] == 15
1305 |> assign(:user, user)
1306 |> get("/users/#{user.nickname}/following?page=2")
1307 |> json_response(200)
1309 assert length(result["orderedItems"]) == 5
1310 assert result["totalItems"] == 15
1313 test "does not require authentication", %{conn: conn} do
1314 user = insert(:user)
1317 |> get("/users/#{user.nickname}/following")
1318 |> json_response(200)
1322 describe "delivery tracking" do
1323 test "it tracks a signed object fetch", %{conn: conn} do
1324 user = insert(:user, local: false)
1325 activity = insert(:note_activity)
1326 object = Object.normalize(activity)
1328 object_path = String.trim_leading(object.data["id"], Pleroma.Web.Endpoint.url())
1331 |> put_req_header("accept", "application/activity+json")
1332 |> assign(:user, user)
1334 |> json_response(200)
1336 assert Delivery.get(object.id, user.id)
1339 test "it tracks a signed activity fetch", %{conn: conn} do
1340 user = insert(:user, local: false)
1341 activity = insert(:note_activity)
1342 object = Object.normalize(activity)
1344 activity_path = String.trim_leading(activity.data["id"], Pleroma.Web.Endpoint.url())
1347 |> put_req_header("accept", "application/activity+json")
1348 |> assign(:user, user)
1349 |> get(activity_path)
1350 |> json_response(200)
1352 assert Delivery.get(object.id, user.id)
1355 test "it tracks a signed object fetch when the json is cached", %{conn: conn} do
1356 user = insert(:user, local: false)
1357 other_user = insert(:user, local: false)
1358 activity = insert(:note_activity)
1359 object = Object.normalize(activity)
1361 object_path = String.trim_leading(object.data["id"], Pleroma.Web.Endpoint.url())
1364 |> put_req_header("accept", "application/activity+json")
1365 |> assign(:user, user)
1367 |> json_response(200)
1370 |> put_req_header("accept", "application/activity+json")
1371 |> assign(:user, other_user)
1373 |> json_response(200)
1375 assert Delivery.get(object.id, user.id)
1376 assert Delivery.get(object.id, other_user.id)
1379 test "it tracks a signed activity fetch when the json is cached", %{conn: conn} do
1380 user = insert(:user, local: false)
1381 other_user = insert(:user, local: false)
1382 activity = insert(:note_activity)
1383 object = Object.normalize(activity)
1385 activity_path = String.trim_leading(activity.data["id"], Pleroma.Web.Endpoint.url())
1388 |> put_req_header("accept", "application/activity+json")
1389 |> assign(:user, user)
1390 |> get(activity_path)
1391 |> json_response(200)
1394 |> put_req_header("accept", "application/activity+json")
1395 |> assign(:user, other_user)
1396 |> get(activity_path)
1397 |> json_response(200)
1399 assert Delivery.get(object.id, user.id)
1400 assert Delivery.get(object.id, other_user.id)
1404 describe "Additional ActivityPub C2S endpoints" do
1405 test "GET /api/ap/whoami", %{conn: conn} do
1406 user = insert(:user)
1410 |> assign(:user, user)
1411 |> get("/api/ap/whoami")
1413 user = User.get_cached_by_id(user.id)
1415 assert UserView.render("user.json", %{user: user}) == json_response(conn, 200)
1418 |> get("/api/ap/whoami")
1419 |> json_response(403)
1422 setup do: clear_config([:media_proxy])
1423 setup do: clear_config([Pleroma.Upload])
1425 test "POST /api/ap/upload_media", %{conn: conn} do
1426 user = insert(:user)
1428 desc = "Description of the image"
1430 image = %Plug.Upload{
1431 content_type: "image/jpg",
1432 path: Path.absname("test/fixtures/image.jpg"),
1433 filename: "an_image.jpg"
1438 |> assign(:user, user)
1439 |> post("/api/ap/upload_media", %{"file" => image, "description" => desc})
1440 |> json_response(:created)
1442 assert object["name"] == desc
1443 assert object["type"] == "Document"
1444 assert object["actor"] == user.ap_id
1445 assert [%{"href" => object_href, "mediaType" => object_mediatype}] = object["url"]
1446 assert is_binary(object_href)
1447 assert object_mediatype == "image/jpeg"
1449 activity_request = %{
1450 "@context" => "https://www.w3.org/ns/activitystreams",
1454 "content" => "AP C2S test, attachment",
1455 "attachment" => [object]
1457 "to" => "https://www.w3.org/ns/activitystreams#Public",
1463 |> assign(:user, user)
1464 |> post("/users/#{user.nickname}/outbox", activity_request)
1465 |> json_response(:created)
1467 assert activity_response["id"]
1468 assert activity_response["object"]
1469 assert activity_response["actor"] == user.ap_id
1471 assert %Object{data: %{"attachment" => [attachment]}} =
1472 Object.normalize(activity_response["object"])
1474 assert attachment["type"] == "Document"
1475 assert attachment["name"] == desc
1479 "href" => ^object_href,
1481 "mediaType" => ^object_mediatype
1483 ] = attachment["url"]
1485 # Fails if unauthenticated
1487 |> post("/api/ap/upload_media", %{"file" => image, "description" => desc})
1488 |> json_response(403)