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 returns 200 even if there're no activities", %{conn: conn} do
812 |> assign(:user, user)
813 |> put_req_header("accept", "application/activity+json")
814 |> get("/users/#{user.nickname}/outbox")
816 result = json_response(conn, 200)
817 assert user.ap_id <> "/outbox" == result["id"]
820 test "it returns a note activity in a collection", %{conn: conn} do
821 note_activity = insert(:note_activity)
822 note_object = Object.normalize(note_activity)
823 user = User.get_cached_by_ap_id(note_activity.data["actor"])
827 |> assign(:user, user)
828 |> put_req_header("accept", "application/activity+json")
829 |> get("/users/#{user.nickname}/outbox?page=true")
831 assert response(conn, 200) =~ note_object.data["content"]
834 test "it returns an announce activity in a collection", %{conn: conn} do
835 announce_activity = insert(:announce_activity)
836 user = User.get_cached_by_ap_id(announce_activity.data["actor"])
840 |> assign(:user, user)
841 |> put_req_header("accept", "application/activity+json")
842 |> get("/users/#{user.nickname}/outbox?page=true")
844 assert response(conn, 200) =~ announce_activity.data["object"]
847 test "it requires authentication if instance is NOT federating", %{
851 conn = put_req_header(conn, "accept", "application/activity+json")
853 ensure_federating_or_authenticated(conn, "/users/#{user.nickname}/outbox", user)
857 describe "POST /users/:nickname/outbox (C2S)" do
861 "@context" => "https://www.w3.org/ns/activitystreams",
863 "object" => %{"type" => "Note", "content" => "AP C2S test"},
864 "to" => "https://www.w3.org/ns/activitystreams#Public",
870 test "it rejects posts from other users / unauthenticated users", %{
875 other_user = insert(:user)
876 conn = put_req_header(conn, "content-type", "application/activity+json")
879 |> post("/users/#{user.nickname}/outbox", activity)
880 |> json_response(403)
883 |> assign(:user, other_user)
884 |> post("/users/#{user.nickname}/outbox", activity)
885 |> json_response(403)
888 test "it inserts an incoming create activity into the database", %{
896 |> assign(:user, user)
897 |> put_req_header("content-type", "application/activity+json")
898 |> post("/users/#{user.nickname}/outbox", activity)
899 |> json_response(201)
901 assert Activity.get_by_ap_id(result["id"])
902 assert result["object"]
903 assert %Object{data: object} = Object.normalize(result["object"])
904 assert object["content"] == activity["object"]["content"]
907 test "it rejects anything beyond 'Note' creations", %{conn: conn, activity: activity} do
912 |> put_in(["object", "type"], "Benis")
916 |> assign(:user, user)
917 |> put_req_header("content-type", "application/activity+json")
918 |> post("/users/#{user.nickname}/outbox", activity)
919 |> json_response(400)
922 test "it inserts an incoming sensitive activity into the database", %{
927 conn = assign(conn, :user, user)
928 object = Map.put(activity["object"], "sensitive", true)
929 activity = Map.put(activity, "object", object)
933 |> put_req_header("content-type", "application/activity+json")
934 |> post("/users/#{user.nickname}/outbox", activity)
935 |> json_response(201)
937 assert Activity.get_by_ap_id(response["id"])
938 assert response["object"]
939 assert %Object{data: response_object} = Object.normalize(response["object"])
940 assert response_object["sensitive"] == true
941 assert response_object["content"] == activity["object"]["content"]
945 |> put_req_header("accept", "application/activity+json")
946 |> get(response["id"])
947 |> json_response(200)
949 assert representation["object"]["sensitive"] == true
952 test "it rejects an incoming activity with bogus type", %{conn: conn, activity: activity} do
954 activity = Map.put(activity, "type", "BadType")
958 |> assign(:user, user)
959 |> put_req_header("content-type", "application/activity+json")
960 |> post("/users/#{user.nickname}/outbox", activity)
962 assert json_response(conn, 400)
965 test "it erects a tombstone when receiving a delete activity", %{conn: conn} do
966 note_activity = insert(:note_activity)
967 note_object = Object.normalize(note_activity)
968 user = User.get_cached_by_ap_id(note_activity.data["actor"])
973 id: note_object.data["id"]
979 |> assign(:user, user)
980 |> put_req_header("content-type", "application/activity+json")
981 |> post("/users/#{user.nickname}/outbox", data)
983 result = json_response(conn, 201)
984 assert Activity.get_by_ap_id(result["id"])
986 assert object = Object.get_by_ap_id(note_object.data["id"])
987 assert object.data["type"] == "Tombstone"
990 test "it rejects delete activity of object from other actor", %{conn: conn} do
991 note_activity = insert(:note_activity)
992 note_object = Object.normalize(note_activity)
998 id: note_object.data["id"]
1004 |> assign(:user, user)
1005 |> put_req_header("content-type", "application/activity+json")
1006 |> post("/users/#{user.nickname}/outbox", data)
1008 assert json_response(conn, 400)
1011 test "it increases like count when receiving a like action", %{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["like_count"] == 1
1037 describe "/relay/followers" do
1038 test "it returns relay followers", %{conn: conn} do
1039 relay_actor = Relay.get_actor()
1040 user = insert(:user)
1041 User.follow(user, relay_actor)
1045 |> get("/relay/followers")
1046 |> json_response(200)
1048 assert result["first"]["orderedItems"] == [user.ap_id]
1051 test "on non-federating instance, it returns 404", %{conn: conn} do
1052 Config.put([:instance, :federating], false)
1053 user = insert(:user)
1056 |> assign(:user, user)
1057 |> get("/relay/followers")
1058 |> json_response(404)
1062 describe "/relay/following" do
1063 test "it returns relay following", %{conn: conn} do
1066 |> get("/relay/following")
1067 |> json_response(200)
1069 assert result["first"]["orderedItems"] == []
1072 test "on non-federating instance, it returns 404", %{conn: conn} do
1073 Config.put([:instance, :federating], false)
1074 user = insert(:user)
1077 |> assign(:user, user)
1078 |> get("/relay/following")
1079 |> json_response(404)
1083 describe "/users/:nickname/followers" do
1084 test "it returns the followers in a collection", %{conn: conn} do
1085 user = insert(:user)
1086 user_two = insert(:user)
1087 User.follow(user, user_two)
1091 |> assign(:user, user_two)
1092 |> get("/users/#{user_two.nickname}/followers")
1093 |> json_response(200)
1095 assert result["first"]["orderedItems"] == [user.ap_id]
1098 test "it returns a uri if the user has 'hide_followers' set", %{conn: conn} do
1099 user = insert(:user)
1100 user_two = insert(:user, hide_followers: true)
1101 User.follow(user, user_two)
1105 |> assign(:user, user)
1106 |> get("/users/#{user_two.nickname}/followers")
1107 |> json_response(200)
1109 assert is_binary(result["first"])
1112 test "it returns a 403 error on pages, if the user has 'hide_followers' set and the request is from another user",
1114 user = insert(:user)
1115 other_user = insert(:user, hide_followers: true)
1119 |> assign(:user, user)
1120 |> get("/users/#{other_user.nickname}/followers?page=1")
1122 assert result.status == 403
1123 assert result.resp_body == ""
1126 test "it renders the page, if the user has 'hide_followers' set and the request is authenticated with the same user",
1128 user = insert(:user, hide_followers: true)
1129 other_user = insert(:user)
1130 {:ok, _other_user, user, _activity} = CommonAPI.follow(other_user, user)
1134 |> assign(:user, user)
1135 |> get("/users/#{user.nickname}/followers?page=1")
1136 |> json_response(200)
1138 assert result["totalItems"] == 1
1139 assert result["orderedItems"] == [other_user.ap_id]
1142 test "it works for more than 10 users", %{conn: conn} do
1143 user = insert(:user)
1145 Enum.each(1..15, fn _ ->
1146 other_user = insert(:user)
1147 User.follow(other_user, user)
1152 |> assign(:user, user)
1153 |> get("/users/#{user.nickname}/followers")
1154 |> json_response(200)
1156 assert length(result["first"]["orderedItems"]) == 10
1157 assert result["first"]["totalItems"] == 15
1158 assert result["totalItems"] == 15
1162 |> assign(:user, user)
1163 |> get("/users/#{user.nickname}/followers?page=2")
1164 |> json_response(200)
1166 assert length(result["orderedItems"]) == 5
1167 assert result["totalItems"] == 15
1170 test "does not require authentication", %{conn: conn} do
1171 user = insert(:user)
1174 |> get("/users/#{user.nickname}/followers")
1175 |> json_response(200)
1179 describe "/users/:nickname/following" do
1180 test "it returns the following in a collection", %{conn: conn} do
1181 user = insert(:user)
1182 user_two = insert(:user)
1183 User.follow(user, user_two)
1187 |> assign(:user, user)
1188 |> get("/users/#{user.nickname}/following")
1189 |> json_response(200)
1191 assert result["first"]["orderedItems"] == [user_two.ap_id]
1194 test "it returns a uri if the user has 'hide_follows' set", %{conn: conn} do
1195 user = insert(:user)
1196 user_two = insert(:user, hide_follows: true)
1197 User.follow(user, user_two)
1201 |> assign(:user, user)
1202 |> get("/users/#{user_two.nickname}/following")
1203 |> json_response(200)
1205 assert is_binary(result["first"])
1208 test "it returns a 403 error on pages, if the user has 'hide_follows' set and the request is from another user",
1210 user = insert(:user)
1211 user_two = insert(:user, hide_follows: true)
1215 |> assign(:user, user)
1216 |> get("/users/#{user_two.nickname}/following?page=1")
1218 assert result.status == 403
1219 assert result.resp_body == ""
1222 test "it renders the page, if the user has 'hide_follows' set and the request is authenticated with the same user",
1224 user = insert(:user, hide_follows: true)
1225 other_user = insert(:user)
1226 {:ok, user, _other_user, _activity} = CommonAPI.follow(user, other_user)
1230 |> assign(:user, user)
1231 |> get("/users/#{user.nickname}/following?page=1")
1232 |> json_response(200)
1234 assert result["totalItems"] == 1
1235 assert result["orderedItems"] == [other_user.ap_id]
1238 test "it works for more than 10 users", %{conn: conn} do
1239 user = insert(:user)
1241 Enum.each(1..15, fn _ ->
1242 user = User.get_cached_by_id(user.id)
1243 other_user = insert(:user)
1244 User.follow(user, other_user)
1249 |> assign(:user, user)
1250 |> get("/users/#{user.nickname}/following")
1251 |> json_response(200)
1253 assert length(result["first"]["orderedItems"]) == 10
1254 assert result["first"]["totalItems"] == 15
1255 assert result["totalItems"] == 15
1259 |> assign(:user, user)
1260 |> get("/users/#{user.nickname}/following?page=2")
1261 |> json_response(200)
1263 assert length(result["orderedItems"]) == 5
1264 assert result["totalItems"] == 15
1267 test "does not require authentication", %{conn: conn} do
1268 user = insert(:user)
1271 |> get("/users/#{user.nickname}/following")
1272 |> json_response(200)
1276 describe "delivery tracking" do
1277 test "it tracks a signed object fetch", %{conn: conn} do
1278 user = insert(:user, local: false)
1279 activity = insert(:note_activity)
1280 object = Object.normalize(activity)
1282 object_path = String.trim_leading(object.data["id"], Pleroma.Web.Endpoint.url())
1285 |> put_req_header("accept", "application/activity+json")
1286 |> assign(:user, user)
1288 |> json_response(200)
1290 assert Delivery.get(object.id, user.id)
1293 test "it tracks a signed activity fetch", %{conn: conn} do
1294 user = insert(:user, local: false)
1295 activity = insert(:note_activity)
1296 object = Object.normalize(activity)
1298 activity_path = String.trim_leading(activity.data["id"], Pleroma.Web.Endpoint.url())
1301 |> put_req_header("accept", "application/activity+json")
1302 |> assign(:user, user)
1303 |> get(activity_path)
1304 |> json_response(200)
1306 assert Delivery.get(object.id, user.id)
1309 test "it tracks a signed object fetch when the json is cached", %{conn: conn} do
1310 user = insert(:user, local: false)
1311 other_user = insert(:user, local: false)
1312 activity = insert(:note_activity)
1313 object = Object.normalize(activity)
1315 object_path = String.trim_leading(object.data["id"], Pleroma.Web.Endpoint.url())
1318 |> put_req_header("accept", "application/activity+json")
1319 |> assign(:user, user)
1321 |> json_response(200)
1324 |> put_req_header("accept", "application/activity+json")
1325 |> assign(:user, other_user)
1327 |> json_response(200)
1329 assert Delivery.get(object.id, user.id)
1330 assert Delivery.get(object.id, other_user.id)
1333 test "it tracks a signed activity fetch when the json is cached", %{conn: conn} do
1334 user = insert(:user, local: false)
1335 other_user = insert(:user, local: false)
1336 activity = insert(:note_activity)
1337 object = Object.normalize(activity)
1339 activity_path = String.trim_leading(activity.data["id"], Pleroma.Web.Endpoint.url())
1342 |> put_req_header("accept", "application/activity+json")
1343 |> assign(:user, user)
1344 |> get(activity_path)
1345 |> json_response(200)
1348 |> put_req_header("accept", "application/activity+json")
1349 |> assign(:user, other_user)
1350 |> get(activity_path)
1351 |> json_response(200)
1353 assert Delivery.get(object.id, user.id)
1354 assert Delivery.get(object.id, other_user.id)
1358 describe "Additional ActivityPub C2S endpoints" do
1359 test "GET /api/ap/whoami", %{conn: conn} do
1360 user = insert(:user)
1364 |> assign(:user, user)
1365 |> get("/api/ap/whoami")
1367 user = User.get_cached_by_id(user.id)
1369 assert UserView.render("user.json", %{user: user}) == json_response(conn, 200)
1372 |> get("/api/ap/whoami")
1373 |> json_response(403)
1376 setup do: clear_config([:media_proxy])
1377 setup do: clear_config([Pleroma.Upload])
1379 test "POST /api/ap/upload_media", %{conn: conn} do
1380 user = insert(:user)
1382 desc = "Description of the image"
1384 image = %Plug.Upload{
1385 content_type: "image/jpg",
1386 path: Path.absname("test/fixtures/image.jpg"),
1387 filename: "an_image.jpg"
1392 |> assign(:user, user)
1393 |> post("/api/ap/upload_media", %{"file" => image, "description" => desc})
1394 |> json_response(:created)
1396 assert object["name"] == desc
1397 assert object["type"] == "Document"
1398 assert object["actor"] == user.ap_id
1399 assert [%{"href" => object_href, "mediaType" => object_mediatype}] = object["url"]
1400 assert is_binary(object_href)
1401 assert object_mediatype == "image/jpeg"
1403 activity_request = %{
1404 "@context" => "https://www.w3.org/ns/activitystreams",
1408 "content" => "AP C2S test, attachment",
1409 "attachment" => [object]
1411 "to" => "https://www.w3.org/ns/activitystreams#Public",
1417 |> assign(:user, user)
1418 |> post("/users/#{user.nickname}/outbox", activity_request)
1419 |> json_response(:created)
1421 assert activity_response["id"]
1422 assert activity_response["object"]
1423 assert activity_response["actor"] == user.ap_id
1425 assert %Object{data: %{"attachment" => [attachment]}} =
1426 Object.normalize(activity_response["object"])
1428 assert attachment["type"] == "Document"
1429 assert attachment["name"] == desc
1433 "href" => ^object_href,
1435 "mediaType" => ^object_mediatype
1437 ] = attachment["url"]
1439 # Fails if unauthenticated
1441 |> post("/api/ap/upload_media", %{"file" => image, "description" => desc})
1442 |> json_response(403)