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
651 {:ok, post} = CommonAPI.post(user, %{status: "hey"})
652 announcer = insert(:user, local: false)
655 "@context" => "https://www.w3.org/ns/activitystreams",
656 "actor" => announcer.ap_id,
657 "id" => "#{announcer.ap_id}/statuses/19512778738411822/activity",
658 "object" => post.data["object"],
659 "to" => "https://www.w3.org/ns/activitystreams#Public",
660 "cc" => [user.ap_id],
666 |> assign(:valid_signature, true)
667 |> put_req_header("content-type", "application/activity+json")
668 |> post("/users/#{user.nickname}/inbox", data)
670 assert "ok" == json_response(conn, 200)
671 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
672 %Activity{} = activity = Activity.get_by_ap_id(data["id"])
673 assert "https://www.w3.org/ns/activitystreams#Public" in activity.recipients
676 test "it accepts messages from actors that are followed by the user", %{
680 recipient = insert(:user)
681 actor = insert(:user, %{ap_id: "http://mastodon.example.org/users/actor"})
683 {:ok, recipient} = User.follow(recipient, actor)
687 |> Map.put("attributedTo", actor.ap_id)
691 |> Map.put("actor", actor.ap_id)
692 |> Map.put("object", object)
696 |> assign(:valid_signature, true)
697 |> put_req_header("content-type", "application/activity+json")
698 |> post("/users/#{recipient.nickname}/inbox", data)
700 assert "ok" == json_response(conn, 200)
701 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
702 assert Activity.get_by_ap_id(data["id"])
705 test "it rejects reads from other users", %{conn: conn} do
707 other_user = insert(:user)
711 |> assign(:user, other_user)
712 |> put_req_header("accept", "application/activity+json")
713 |> get("/users/#{user.nickname}/inbox")
715 assert json_response(conn, 403)
718 test "it returns a note activity in a collection", %{conn: conn} do
719 note_activity = insert(:direct_note_activity)
720 note_object = Object.normalize(note_activity)
721 user = User.get_cached_by_ap_id(hd(note_activity.data["to"]))
725 |> assign(:user, user)
726 |> put_req_header("accept", "application/activity+json")
727 |> get("/users/#{user.nickname}/inbox?page=true")
729 assert response(conn, 200) =~ note_object.data["content"]
732 test "it clears `unreachable` federation status of the sender", %{conn: conn, data: data} do
734 data = Map.put(data, "bcc", [user.ap_id])
736 sender_host = URI.parse(data["actor"]).host
737 Instances.set_consistently_unreachable(sender_host)
738 refute Instances.reachable?(sender_host)
742 |> assign(:valid_signature, true)
743 |> put_req_header("content-type", "application/activity+json")
744 |> post("/users/#{user.nickname}/inbox", data)
746 assert "ok" == json_response(conn, 200)
747 assert Instances.reachable?(sender_host)
750 test "it removes all follower collections but actor's", %{conn: conn} do
751 [actor, recipient] = insert_pair(:user)
754 File.read!("test/fixtures/activitypub-client-post-activity.json")
757 object = Map.put(data["object"], "attributedTo", actor.ap_id)
761 |> Map.put("id", Utils.generate_object_id())
762 |> Map.put("actor", actor.ap_id)
763 |> Map.put("object", object)
765 recipient.follower_address,
766 actor.follower_address
770 recipient.follower_address,
771 "https://www.w3.org/ns/activitystreams#Public"
775 |> assign(:valid_signature, true)
776 |> put_req_header("content-type", "application/activity+json")
777 |> post("/users/#{recipient.nickname}/inbox", data)
778 |> json_response(200)
780 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
782 activity = Activity.get_by_ap_id(data["id"])
785 assert actor.follower_address in activity.recipients
786 assert actor.follower_address in activity.data["cc"]
788 refute recipient.follower_address in activity.recipients
789 refute recipient.follower_address in activity.data["cc"]
790 refute recipient.follower_address in activity.data["to"]
793 test "it requires authentication", %{conn: conn} do
795 conn = put_req_header(conn, "accept", "application/activity+json")
797 ret_conn = get(conn, "/users/#{user.nickname}/inbox")
798 assert json_response(ret_conn, 403)
802 |> assign(:user, user)
803 |> get("/users/#{user.nickname}/inbox")
805 assert json_response(ret_conn, 200)
809 describe "GET /users/:nickname/outbox" do
810 test "it paginates correctly", %{conn: conn} do
812 conn = assign(conn, :user, user)
813 outbox_endpoint = user.ap_id <> "/outbox"
817 {:ok, activity} = CommonAPI.post(user, %{status: "post #{i}"})
823 |> put_req_header("accept", "application/activity+json")
824 |> get(outbox_endpoint <> "?page=true")
825 |> json_response(200)
827 result_ids = Enum.map(result["orderedItems"], fn x -> x["id"] end)
828 assert length(result["orderedItems"]) == 20
829 assert length(result_ids) == 20
830 assert result["next"]
831 assert String.starts_with?(result["next"], outbox_endpoint)
835 |> put_req_header("accept", "application/activity+json")
836 |> get(result["next"])
837 |> json_response(200)
839 result_next_ids = Enum.map(result_next["orderedItems"], fn x -> x["id"] end)
840 assert length(result_next["orderedItems"]) == 6
841 assert length(result_next_ids) == 6
842 refute Enum.find(result_next_ids, fn x -> x in result_ids end)
843 refute Enum.find(result_ids, fn x -> x in result_next_ids end)
844 assert String.starts_with?(result["id"], outbox_endpoint)
848 |> put_req_header("accept", "application/activity+json")
849 |> get(result_next["id"])
850 |> json_response(200)
852 assert result_next == result_next_again
855 test "it returns 200 even if there're no activities", %{conn: conn} do
857 outbox_endpoint = user.ap_id <> "/outbox"
861 |> assign(:user, user)
862 |> put_req_header("accept", "application/activity+json")
863 |> get(outbox_endpoint)
865 result = json_response(conn, 200)
866 assert outbox_endpoint == result["id"]
869 test "it returns a note activity in a collection", %{conn: conn} do
870 note_activity = insert(:note_activity)
871 note_object = Object.normalize(note_activity)
872 user = User.get_cached_by_ap_id(note_activity.data["actor"])
876 |> assign(:user, user)
877 |> put_req_header("accept", "application/activity+json")
878 |> get("/users/#{user.nickname}/outbox?page=true")
880 assert response(conn, 200) =~ note_object.data["content"]
883 test "it returns an announce activity in a collection", %{conn: conn} do
884 announce_activity = insert(:announce_activity)
885 user = User.get_cached_by_ap_id(announce_activity.data["actor"])
889 |> assign(:user, user)
890 |> put_req_header("accept", "application/activity+json")
891 |> get("/users/#{user.nickname}/outbox?page=true")
893 assert response(conn, 200) =~ announce_activity.data["object"]
896 test "it requires authentication if instance is NOT federating", %{
900 conn = put_req_header(conn, "accept", "application/activity+json")
902 ensure_federating_or_authenticated(conn, "/users/#{user.nickname}/outbox", user)
906 describe "POST /users/:nickname/outbox (C2S)" do
910 "@context" => "https://www.w3.org/ns/activitystreams",
912 "object" => %{"type" => "Note", "content" => "AP C2S test"},
913 "to" => "https://www.w3.org/ns/activitystreams#Public",
919 test "it rejects posts from other users / unauthenticated users", %{
924 other_user = insert(:user)
925 conn = put_req_header(conn, "content-type", "application/activity+json")
928 |> post("/users/#{user.nickname}/outbox", activity)
929 |> json_response(403)
932 |> assign(:user, other_user)
933 |> post("/users/#{user.nickname}/outbox", activity)
934 |> json_response(403)
937 test "it inserts an incoming create activity into the database", %{
945 |> assign(:user, user)
946 |> put_req_header("content-type", "application/activity+json")
947 |> post("/users/#{user.nickname}/outbox", activity)
948 |> json_response(201)
950 assert Activity.get_by_ap_id(result["id"])
951 assert result["object"]
952 assert %Object{data: object} = Object.normalize(result["object"])
953 assert object["content"] == activity["object"]["content"]
956 test "it rejects anything beyond 'Note' creations", %{conn: conn, activity: activity} do
961 |> put_in(["object", "type"], "Benis")
965 |> assign(:user, user)
966 |> put_req_header("content-type", "application/activity+json")
967 |> post("/users/#{user.nickname}/outbox", activity)
968 |> json_response(400)
971 test "it inserts an incoming sensitive activity into the database", %{
976 conn = assign(conn, :user, user)
977 object = Map.put(activity["object"], "sensitive", true)
978 activity = Map.put(activity, "object", object)
982 |> put_req_header("content-type", "application/activity+json")
983 |> post("/users/#{user.nickname}/outbox", activity)
984 |> json_response(201)
986 assert Activity.get_by_ap_id(response["id"])
987 assert response["object"]
988 assert %Object{data: response_object} = Object.normalize(response["object"])
989 assert response_object["sensitive"] == true
990 assert response_object["content"] == activity["object"]["content"]
994 |> put_req_header("accept", "application/activity+json")
995 |> get(response["id"])
996 |> json_response(200)
998 assert representation["object"]["sensitive"] == true
1001 test "it rejects an incoming activity with bogus type", %{conn: conn, activity: activity} do
1002 user = insert(:user)
1003 activity = Map.put(activity, "type", "BadType")
1007 |> assign(:user, user)
1008 |> put_req_header("content-type", "application/activity+json")
1009 |> post("/users/#{user.nickname}/outbox", activity)
1011 assert json_response(conn, 400)
1014 test "it erects a tombstone when receiving a delete activity", %{conn: conn} do
1015 note_activity = insert(:note_activity)
1016 note_object = Object.normalize(note_activity)
1017 user = User.get_cached_by_ap_id(note_activity.data["actor"])
1022 id: note_object.data["id"]
1028 |> assign(:user, user)
1029 |> put_req_header("content-type", "application/activity+json")
1030 |> post("/users/#{user.nickname}/outbox", data)
1032 result = json_response(conn, 201)
1033 assert Activity.get_by_ap_id(result["id"])
1035 assert object = Object.get_by_ap_id(note_object.data["id"])
1036 assert object.data["type"] == "Tombstone"
1039 test "it rejects delete activity of object from other actor", %{conn: conn} do
1040 note_activity = insert(:note_activity)
1041 note_object = Object.normalize(note_activity)
1042 user = insert(:user)
1047 id: note_object.data["id"]
1053 |> assign(:user, user)
1054 |> put_req_header("content-type", "application/activity+json")
1055 |> post("/users/#{user.nickname}/outbox", data)
1057 assert json_response(conn, 400)
1060 test "it increases like count when receiving a like action", %{conn: conn} do
1061 note_activity = insert(:note_activity)
1062 note_object = Object.normalize(note_activity)
1063 user = User.get_cached_by_ap_id(note_activity.data["actor"])
1068 id: note_object.data["id"]
1074 |> assign(:user, user)
1075 |> put_req_header("content-type", "application/activity+json")
1076 |> post("/users/#{user.nickname}/outbox", data)
1078 result = json_response(conn, 201)
1079 assert Activity.get_by_ap_id(result["id"])
1081 assert object = Object.get_by_ap_id(note_object.data["id"])
1082 assert object.data["like_count"] == 1
1086 describe "/relay/followers" do
1087 test "it returns relay followers", %{conn: conn} do
1088 relay_actor = Relay.get_actor()
1089 user = insert(:user)
1090 User.follow(user, relay_actor)
1094 |> get("/relay/followers")
1095 |> json_response(200)
1097 assert result["first"]["orderedItems"] == [user.ap_id]
1100 test "on non-federating instance, it returns 404", %{conn: conn} do
1101 Config.put([:instance, :federating], false)
1102 user = insert(:user)
1105 |> assign(:user, user)
1106 |> get("/relay/followers")
1107 |> json_response(404)
1111 describe "/relay/following" do
1112 test "it returns relay following", %{conn: conn} do
1115 |> get("/relay/following")
1116 |> json_response(200)
1118 assert result["first"]["orderedItems"] == []
1121 test "on non-federating instance, it returns 404", %{conn: conn} do
1122 Config.put([:instance, :federating], false)
1123 user = insert(:user)
1126 |> assign(:user, user)
1127 |> get("/relay/following")
1128 |> json_response(404)
1132 describe "/users/:nickname/followers" do
1133 test "it returns the followers in a collection", %{conn: conn} do
1134 user = insert(:user)
1135 user_two = insert(:user)
1136 User.follow(user, user_two)
1140 |> assign(:user, user_two)
1141 |> get("/users/#{user_two.nickname}/followers")
1142 |> json_response(200)
1144 assert result["first"]["orderedItems"] == [user.ap_id]
1147 test "it returns a uri if the user has 'hide_followers' set", %{conn: conn} do
1148 user = insert(:user)
1149 user_two = insert(:user, hide_followers: true)
1150 User.follow(user, user_two)
1154 |> assign(:user, user)
1155 |> get("/users/#{user_two.nickname}/followers")
1156 |> json_response(200)
1158 assert is_binary(result["first"])
1161 test "it returns a 403 error on pages, if the user has 'hide_followers' set and the request is from another user",
1163 user = insert(:user)
1164 other_user = insert(:user, hide_followers: true)
1168 |> assign(:user, user)
1169 |> get("/users/#{other_user.nickname}/followers?page=1")
1171 assert result.status == 403
1172 assert result.resp_body == ""
1175 test "it renders the page, if the user has 'hide_followers' set and the request is authenticated with the same user",
1177 user = insert(:user, hide_followers: true)
1178 other_user = insert(:user)
1179 {:ok, _other_user, user, _activity} = CommonAPI.follow(other_user, user)
1183 |> assign(:user, user)
1184 |> get("/users/#{user.nickname}/followers?page=1")
1185 |> json_response(200)
1187 assert result["totalItems"] == 1
1188 assert result["orderedItems"] == [other_user.ap_id]
1191 test "it works for more than 10 users", %{conn: conn} do
1192 user = insert(:user)
1194 Enum.each(1..15, fn _ ->
1195 other_user = insert(:user)
1196 User.follow(other_user, user)
1201 |> assign(:user, user)
1202 |> get("/users/#{user.nickname}/followers")
1203 |> json_response(200)
1205 assert length(result["first"]["orderedItems"]) == 10
1206 assert result["first"]["totalItems"] == 15
1207 assert result["totalItems"] == 15
1211 |> assign(:user, user)
1212 |> get("/users/#{user.nickname}/followers?page=2")
1213 |> json_response(200)
1215 assert length(result["orderedItems"]) == 5
1216 assert result["totalItems"] == 15
1219 test "does not require authentication", %{conn: conn} do
1220 user = insert(:user)
1223 |> get("/users/#{user.nickname}/followers")
1224 |> json_response(200)
1228 describe "/users/:nickname/following" do
1229 test "it returns the following in a collection", %{conn: conn} do
1230 user = insert(:user)
1231 user_two = insert(:user)
1232 User.follow(user, user_two)
1236 |> assign(:user, user)
1237 |> get("/users/#{user.nickname}/following")
1238 |> json_response(200)
1240 assert result["first"]["orderedItems"] == [user_two.ap_id]
1243 test "it returns a uri if the user has 'hide_follows' set", %{conn: conn} do
1244 user = insert(:user)
1245 user_two = insert(:user, hide_follows: true)
1246 User.follow(user, user_two)
1250 |> assign(:user, user)
1251 |> get("/users/#{user_two.nickname}/following")
1252 |> json_response(200)
1254 assert is_binary(result["first"])
1257 test "it returns a 403 error on pages, if the user has 'hide_follows' set and the request is from another user",
1259 user = insert(:user)
1260 user_two = insert(:user, hide_follows: true)
1264 |> assign(:user, user)
1265 |> get("/users/#{user_two.nickname}/following?page=1")
1267 assert result.status == 403
1268 assert result.resp_body == ""
1271 test "it renders the page, if the user has 'hide_follows' set and the request is authenticated with the same user",
1273 user = insert(:user, hide_follows: true)
1274 other_user = insert(:user)
1275 {:ok, user, _other_user, _activity} = CommonAPI.follow(user, other_user)
1279 |> assign(:user, user)
1280 |> get("/users/#{user.nickname}/following?page=1")
1281 |> json_response(200)
1283 assert result["totalItems"] == 1
1284 assert result["orderedItems"] == [other_user.ap_id]
1287 test "it works for more than 10 users", %{conn: conn} do
1288 user = insert(:user)
1290 Enum.each(1..15, fn _ ->
1291 user = User.get_cached_by_id(user.id)
1292 other_user = insert(:user)
1293 User.follow(user, other_user)
1298 |> assign(:user, user)
1299 |> get("/users/#{user.nickname}/following")
1300 |> json_response(200)
1302 assert length(result["first"]["orderedItems"]) == 10
1303 assert result["first"]["totalItems"] == 15
1304 assert result["totalItems"] == 15
1308 |> assign(:user, user)
1309 |> get("/users/#{user.nickname}/following?page=2")
1310 |> json_response(200)
1312 assert length(result["orderedItems"]) == 5
1313 assert result["totalItems"] == 15
1316 test "does not require authentication", %{conn: conn} do
1317 user = insert(:user)
1320 |> get("/users/#{user.nickname}/following")
1321 |> json_response(200)
1325 describe "delivery tracking" do
1326 test "it tracks a signed object fetch", %{conn: conn} do
1327 user = insert(:user, local: false)
1328 activity = insert(:note_activity)
1329 object = Object.normalize(activity)
1331 object_path = String.trim_leading(object.data["id"], Pleroma.Web.Endpoint.url())
1334 |> put_req_header("accept", "application/activity+json")
1335 |> assign(:user, user)
1337 |> json_response(200)
1339 assert Delivery.get(object.id, user.id)
1342 test "it tracks a signed activity fetch", %{conn: conn} do
1343 user = insert(:user, local: false)
1344 activity = insert(:note_activity)
1345 object = Object.normalize(activity)
1347 activity_path = String.trim_leading(activity.data["id"], Pleroma.Web.Endpoint.url())
1350 |> put_req_header("accept", "application/activity+json")
1351 |> assign(:user, user)
1352 |> get(activity_path)
1353 |> json_response(200)
1355 assert Delivery.get(object.id, user.id)
1358 test "it tracks a signed object fetch when the json is cached", %{conn: conn} do
1359 user = insert(:user, local: false)
1360 other_user = insert(:user, local: false)
1361 activity = insert(:note_activity)
1362 object = Object.normalize(activity)
1364 object_path = String.trim_leading(object.data["id"], Pleroma.Web.Endpoint.url())
1367 |> put_req_header("accept", "application/activity+json")
1368 |> assign(:user, user)
1370 |> json_response(200)
1373 |> put_req_header("accept", "application/activity+json")
1374 |> assign(:user, other_user)
1376 |> json_response(200)
1378 assert Delivery.get(object.id, user.id)
1379 assert Delivery.get(object.id, other_user.id)
1382 test "it tracks a signed activity fetch when the json is cached", %{conn: conn} do
1383 user = insert(:user, local: false)
1384 other_user = insert(:user, local: false)
1385 activity = insert(:note_activity)
1386 object = Object.normalize(activity)
1388 activity_path = String.trim_leading(activity.data["id"], Pleroma.Web.Endpoint.url())
1391 |> put_req_header("accept", "application/activity+json")
1392 |> assign(:user, user)
1393 |> get(activity_path)
1394 |> json_response(200)
1397 |> put_req_header("accept", "application/activity+json")
1398 |> assign(:user, other_user)
1399 |> get(activity_path)
1400 |> json_response(200)
1402 assert Delivery.get(object.id, user.id)
1403 assert Delivery.get(object.id, other_user.id)
1407 describe "Additional ActivityPub C2S endpoints" do
1408 test "GET /api/ap/whoami", %{conn: conn} do
1409 user = insert(:user)
1413 |> assign(:user, user)
1414 |> get("/api/ap/whoami")
1416 user = User.get_cached_by_id(user.id)
1418 assert UserView.render("user.json", %{user: user}) == json_response(conn, 200)
1421 |> get("/api/ap/whoami")
1422 |> json_response(403)
1425 setup do: clear_config([:media_proxy])
1426 setup do: clear_config([Pleroma.Upload])
1428 test "POST /api/ap/upload_media", %{conn: conn} do
1429 user = insert(:user)
1431 desc = "Description of the image"
1433 image = %Plug.Upload{
1434 content_type: "image/jpg",
1435 path: Path.absname("test/fixtures/image.jpg"),
1436 filename: "an_image.jpg"
1441 |> assign(:user, user)
1442 |> post("/api/ap/upload_media", %{"file" => image, "description" => desc})
1443 |> json_response(:created)
1445 assert object["name"] == desc
1446 assert object["type"] == "Document"
1447 assert object["actor"] == user.ap_id
1448 assert [%{"href" => object_href, "mediaType" => object_mediatype}] = object["url"]
1449 assert is_binary(object_href)
1450 assert object_mediatype == "image/jpeg"
1452 activity_request = %{
1453 "@context" => "https://www.w3.org/ns/activitystreams",
1457 "content" => "AP C2S test, attachment",
1458 "attachment" => [object]
1460 "to" => "https://www.w3.org/ns/activitystreams#Public",
1466 |> assign(:user, user)
1467 |> post("/users/#{user.nickname}/outbox", activity_request)
1468 |> json_response(:created)
1470 assert activity_response["id"]
1471 assert activity_response["object"]
1472 assert activity_response["actor"] == user.ap_id
1474 assert %Object{data: %{"attachment" => [attachment]}} =
1475 Object.normalize(activity_response["object"])
1477 assert attachment["type"] == "Document"
1478 assert attachment["name"] == desc
1482 "href" => ^object_href,
1484 "mediaType" => ^object_mediatype
1486 ] = attachment["url"]
1488 # Fails if unauthenticated
1490 |> post("/api/ap/upload_media", %{"file" => image, "description" => desc})
1491 |> json_response(403)