1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2021 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
10 alias Pleroma.Delivery
11 alias Pleroma.Instances
13 alias Pleroma.Tests.ObanHelpers
15 alias Pleroma.Web.ActivityPub.ActivityPub
16 alias Pleroma.Web.ActivityPub.ObjectView
17 alias Pleroma.Web.ActivityPub.Relay
18 alias Pleroma.Web.ActivityPub.UserView
19 alias Pleroma.Web.ActivityPub.Utils
20 alias Pleroma.Web.CommonAPI
21 alias Pleroma.Web.Endpoint
22 alias Pleroma.Workers.ReceiverWorker
24 import Pleroma.Factory
26 require Pleroma.Constants
29 Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
33 setup do: clear_config([:instance, :federating], true)
36 setup do: clear_config([:instance, :allow_relay], true)
38 test "with the relay active, it returns the relay user", %{conn: conn} do
41 |> get(activity_pub_path(conn, :relay))
44 assert res["id"] =~ "/relay"
47 test "with the relay disabled, it returns 404", %{conn: conn} do
48 clear_config([:instance, :allow_relay], false)
51 |> get(activity_pub_path(conn, :relay))
55 test "on non-federating instance, it returns 404", %{conn: conn} do
56 clear_config([:instance, :federating], false)
60 |> assign(:user, user)
61 |> get(activity_pub_path(conn, :relay))
66 describe "/internal/fetch" do
67 test "it returns the internal fetch user", %{conn: conn} do
70 |> get(activity_pub_path(conn, :internal_fetch))
73 assert res["id"] =~ "/fetch"
76 test "on non-federating instance, it returns 404", %{conn: conn} do
77 clear_config([:instance, :federating], false)
81 |> assign(:user, user)
82 |> get(activity_pub_path(conn, :internal_fetch))
87 describe "/users/:nickname" do
88 test "it returns a json representation of the user with accept application/json", %{
95 |> put_req_header("accept", "application/json")
96 |> get("/users/#{user.nickname}")
98 user = User.get_cached_by_id(user.id)
100 assert json_response(conn, 200) == UserView.render("user.json", %{user: user})
103 test "it returns a json representation of the user with accept application/activity+json", %{
110 |> put_req_header("accept", "application/activity+json")
111 |> get("/users/#{user.nickname}")
113 user = User.get_cached_by_id(user.id)
115 assert json_response(conn, 200) == UserView.render("user.json", %{user: user})
118 test "it returns a json representation of the user with accept application/ld+json", %{
127 "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\""
129 |> get("/users/#{user.nickname}")
131 user = User.get_cached_by_id(user.id)
133 assert json_response(conn, 200) == UserView.render("user.json", %{user: user})
136 test "it returns 404 for remote users", %{
139 user = insert(:user, local: false, nickname: "remoteuser@example.com")
143 |> put_req_header("accept", "application/json")
144 |> get("/users/#{user.nickname}.json")
146 assert json_response(conn, 404)
149 test "it returns error when user is not found", %{conn: conn} do
152 |> put_req_header("accept", "application/json")
153 |> get("/users/jimm")
154 |> json_response(404)
156 assert response == "Not found"
160 describe "mastodon compatibility routes" do
161 test "it returns a json representation of the object with accept application/json", %{
168 "id" => Endpoint.url() <> "/users/raymoo/statuses/999999999",
169 "actor" => Endpoint.url() <> "/users/raymoo",
170 "to" => [Pleroma.Constants.as_public()]
176 |> put_req_header("accept", "application/json")
177 |> get("/users/raymoo/statuses/999999999")
179 assert json_response(conn, 200) == ObjectView.render("object.json", %{object: object})
182 test "it returns a json representation of the activity with accept application/json", %{
189 "id" => Endpoint.url() <> "/users/raymoo/statuses/999999999",
190 "actor" => Endpoint.url() <> "/users/raymoo",
191 "to" => [Pleroma.Constants.as_public()]
197 "id" => object.data["id"] <> "/activity",
199 "object" => object.data["id"],
200 "actor" => object.data["actor"],
201 "to" => object.data["to"]
203 |> ActivityPub.persist(local: true)
207 |> put_req_header("accept", "application/json")
208 |> get("/users/raymoo/statuses/999999999/activity")
210 assert json_response(conn, 200) == ObjectView.render("object.json", %{object: activity})
214 describe "/objects/:uuid" do
215 test "it doesn't return a local-only object", %{conn: conn} do
217 {:ok, post} = CommonAPI.post(user, %{status: "test", visibility: "local"})
219 assert Pleroma.Web.ActivityPub.Visibility.is_local_public?(post)
221 object = Object.normalize(post, fetch: false)
222 uuid = String.split(object.data["id"], "/") |> List.last()
226 |> put_req_header("accept", "application/json")
227 |> get("/objects/#{uuid}")
229 assert json_response(conn, 404)
232 test "returns local-only objects when authenticated", %{conn: conn} do
234 {:ok, post} = CommonAPI.post(user, %{status: "test", visibility: "local"})
236 assert Pleroma.Web.ActivityPub.Visibility.is_local_public?(post)
238 object = Object.normalize(post, fetch: false)
239 uuid = String.split(object.data["id"], "/") |> List.last()
243 |> assign(:user, user)
244 |> put_req_header("accept", "application/activity+json")
245 |> get("/objects/#{uuid}")
247 assert json_response(response, 200) == ObjectView.render("object.json", %{object: object})
250 test "does not return local-only objects for remote users", %{conn: conn} do
252 reader = insert(:user, local: false)
255 CommonAPI.post(user, %{status: "test @#{reader.nickname}", visibility: "local"})
257 assert Pleroma.Web.ActivityPub.Visibility.is_local_public?(post)
259 object = Object.normalize(post, fetch: false)
260 uuid = String.split(object.data["id"], "/") |> List.last()
264 |> assign(:user, reader)
265 |> put_req_header("accept", "application/activity+json")
266 |> get("/objects/#{uuid}")
268 json_response(response, 404)
271 test "it returns a json representation of the object with accept application/json", %{
275 uuid = String.split(note.data["id"], "/") |> List.last()
279 |> put_req_header("accept", "application/json")
280 |> get("/objects/#{uuid}")
282 assert json_response(conn, 200) == ObjectView.render("object.json", %{object: note})
285 test "it returns a json representation of the object with accept application/activity+json",
288 uuid = String.split(note.data["id"], "/") |> List.last()
292 |> put_req_header("accept", "application/activity+json")
293 |> get("/objects/#{uuid}")
295 assert json_response(conn, 200) == ObjectView.render("object.json", %{object: note})
298 test "it returns a json representation of the object with accept application/ld+json", %{
302 uuid = String.split(note.data["id"], "/") |> List.last()
308 "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\""
310 |> get("/objects/#{uuid}")
312 assert json_response(conn, 200) == ObjectView.render("object.json", %{object: note})
315 test "does not cache authenticated response", %{conn: conn} do
317 reader = insert(:user)
320 CommonAPI.post(user, %{status: "test @#{reader.nickname}", visibility: "local"})
322 object = Object.normalize(post, fetch: false)
323 uuid = String.split(object.data["id"], "/") |> List.last()
327 |> assign(:user, reader)
328 |> put_req_header("accept", "application/activity+json")
329 |> get("/objects/#{uuid}")
331 json_response(response, 200)
334 |> put_req_header("accept", "application/activity+json")
335 |> get("/objects/#{uuid}")
336 |> json_response(404)
339 test "it returns 404 for non-public messages", %{conn: conn} do
340 note = insert(:direct_note)
341 uuid = String.split(note.data["id"], "/") |> List.last()
345 |> put_req_header("accept", "application/activity+json")
346 |> get("/objects/#{uuid}")
348 assert json_response(conn, 404)
351 test "returns visible non-public messages when authenticated", %{conn: conn} do
352 note = insert(:direct_note)
353 uuid = String.split(note.data["id"], "/") |> List.last()
354 user = User.get_by_ap_id(note.data["actor"])
355 marisa = insert(:user)
358 |> assign(:user, marisa)
359 |> put_req_header("accept", "application/activity+json")
360 |> get("/objects/#{uuid}")
361 |> json_response(404)
365 |> assign(:user, user)
366 |> put_req_header("accept", "application/activity+json")
367 |> get("/objects/#{uuid}")
368 |> json_response(200)
370 assert response == ObjectView.render("object.json", %{object: note})
373 test "it returns 404 for tombstone objects", %{conn: conn} do
374 tombstone = insert(:tombstone)
375 uuid = String.split(tombstone.data["id"], "/") |> List.last()
379 |> put_req_header("accept", "application/activity+json")
380 |> get("/objects/#{uuid}")
382 assert json_response(conn, 404)
385 test "it caches a response", %{conn: conn} do
387 uuid = String.split(note.data["id"], "/") |> List.last()
391 |> put_req_header("accept", "application/activity+json")
392 |> get("/objects/#{uuid}")
394 assert json_response(conn1, :ok)
395 assert Enum.any?(conn1.resp_headers, &(&1 == {"x-cache", "MISS from Pleroma"}))
399 |> put_req_header("accept", "application/activity+json")
400 |> get("/objects/#{uuid}")
402 assert json_response(conn1, :ok) == json_response(conn2, :ok)
403 assert Enum.any?(conn2.resp_headers, &(&1 == {"x-cache", "HIT from Pleroma"}))
406 test "cached purged after object deletion", %{conn: conn} do
408 uuid = String.split(note.data["id"], "/") |> List.last()
412 |> put_req_header("accept", "application/activity+json")
413 |> get("/objects/#{uuid}")
415 assert json_response(conn1, :ok)
416 assert Enum.any?(conn1.resp_headers, &(&1 == {"x-cache", "MISS from Pleroma"}))
422 |> put_req_header("accept", "application/activity+json")
423 |> get("/objects/#{uuid}")
425 assert "Not found" == json_response(conn2, :not_found)
429 describe "/activities/:uuid" do
430 test "it doesn't return a local-only activity", %{conn: conn} do
432 {:ok, post} = CommonAPI.post(user, %{status: "test", visibility: "local"})
434 assert Pleroma.Web.ActivityPub.Visibility.is_local_public?(post)
436 uuid = String.split(post.data["id"], "/") |> List.last()
440 |> put_req_header("accept", "application/json")
441 |> get("/activities/#{uuid}")
443 assert json_response(conn, 404)
446 test "returns local-only activities when authenticated", %{conn: conn} do
448 {:ok, post} = CommonAPI.post(user, %{status: "test", visibility: "local"})
450 assert Pleroma.Web.ActivityPub.Visibility.is_local_public?(post)
452 uuid = String.split(post.data["id"], "/") |> List.last()
456 |> assign(:user, user)
457 |> put_req_header("accept", "application/activity+json")
458 |> get("/activities/#{uuid}")
460 assert json_response(response, 200) == ObjectView.render("object.json", %{object: post})
463 test "it returns a json representation of the activity", %{conn: conn} do
464 activity = insert(:note_activity)
465 uuid = String.split(activity.data["id"], "/") |> List.last()
469 |> put_req_header("accept", "application/activity+json")
470 |> get("/activities/#{uuid}")
472 assert json_response(conn, 200) == ObjectView.render("object.json", %{object: activity})
475 test "it returns 404 for non-public activities", %{conn: conn} do
476 activity = insert(:direct_note_activity)
477 uuid = String.split(activity.data["id"], "/") |> List.last()
481 |> put_req_header("accept", "application/activity+json")
482 |> get("/activities/#{uuid}")
484 assert json_response(conn, 404)
487 test "returns visible non-public messages when authenticated", %{conn: conn} do
488 note = insert(:direct_note_activity)
489 uuid = String.split(note.data["id"], "/") |> List.last()
490 user = User.get_by_ap_id(note.data["actor"])
491 marisa = insert(:user)
494 |> assign(:user, marisa)
495 |> put_req_header("accept", "application/activity+json")
496 |> get("/activities/#{uuid}")
497 |> json_response(404)
501 |> assign(:user, user)
502 |> put_req_header("accept", "application/activity+json")
503 |> get("/activities/#{uuid}")
504 |> json_response(200)
506 assert response == ObjectView.render("object.json", %{object: note})
509 test "it caches a response", %{conn: conn} do
510 activity = insert(:note_activity)
511 uuid = String.split(activity.data["id"], "/") |> List.last()
515 |> put_req_header("accept", "application/activity+json")
516 |> get("/activities/#{uuid}")
518 assert json_response(conn1, :ok)
519 assert Enum.any?(conn1.resp_headers, &(&1 == {"x-cache", "MISS from Pleroma"}))
523 |> put_req_header("accept", "application/activity+json")
524 |> get("/activities/#{uuid}")
526 assert json_response(conn1, :ok) == json_response(conn2, :ok)
527 assert Enum.any?(conn2.resp_headers, &(&1 == {"x-cache", "HIT from Pleroma"}))
530 test "cached purged after activity deletion", %{conn: conn} do
532 {:ok, activity} = CommonAPI.post(user, %{status: "cofe"})
534 uuid = String.split(activity.data["id"], "/") |> List.last()
538 |> put_req_header("accept", "application/activity+json")
539 |> get("/activities/#{uuid}")
541 assert json_response(conn1, :ok)
542 assert Enum.any?(conn1.resp_headers, &(&1 == {"x-cache", "MISS from Pleroma"}))
544 Activity.delete_all_by_object_ap_id(activity.object.data["id"])
548 |> put_req_header("accept", "application/activity+json")
549 |> get("/activities/#{uuid}")
551 assert "Not found" == json_response(conn2, :not_found)
556 test "it inserts an incoming activity into the database", %{conn: conn} do
557 data = File.read!("test/fixtures/mastodon-post-activity.json") |> Jason.decode!()
561 |> assign(:valid_signature, true)
564 "keyId=\"http://mastodon.example.org/users/admin/main-key\""
566 |> put_req_header("content-type", "application/activity+json")
567 |> post("/inbox", data)
569 assert "ok" == json_response(conn, 200)
571 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
572 assert Activity.get_by_ap_id(data["id"])
575 @tag capture_log: true
576 test "it inserts an incoming activity into the database" <>
577 "even if we can't fetch the user but have it in our db",
581 ap_id: "https://mastodon.example.org/users/raymoo",
584 last_refreshed_at: nil
588 File.read!("test/fixtures/mastodon-post-activity.json")
590 |> Map.put("actor", user.ap_id)
591 |> put_in(["object", "attributedTo"], user.ap_id)
595 |> assign(:valid_signature, true)
596 |> put_req_header("signature", "keyId=\"#{user.ap_id}/main-key\"")
597 |> put_req_header("content-type", "application/activity+json")
598 |> post("/inbox", data)
600 assert "ok" == json_response(conn, 200)
602 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
603 assert Activity.get_by_ap_id(data["id"])
606 test "it clears `unreachable` federation status of the sender", %{conn: conn} do
607 data = File.read!("test/fixtures/mastodon-post-activity.json") |> Jason.decode!()
609 sender_url = data["actor"]
610 sender = insert(:user, ap_id: data["actor"])
612 Instances.set_consistently_unreachable(sender_url)
613 refute Instances.reachable?(sender_url)
617 |> assign(:valid_signature, true)
618 |> put_req_header("signature", "keyId=\"#{sender.ap_id}/main-key\"")
619 |> put_req_header("content-type", "application/activity+json")
620 |> post("/inbox", data)
622 assert "ok" == json_response(conn, 200)
623 assert Instances.reachable?(sender_url)
626 test "accept follow activity", %{conn: conn} do
627 clear_config([:instance, :federating], true)
628 relay = Relay.get_actor()
630 assert {:ok, %Activity{} = activity} = Relay.follow("https://relay.mastodon.host/actor")
632 followed_relay = Pleroma.User.get_by_ap_id("https://relay.mastodon.host/actor")
633 relay = refresh_record(relay)
636 File.read!("test/fixtures/relay/accept-follow.json")
637 |> String.replace("{{ap_id}}", relay.ap_id)
638 |> String.replace("{{activity_id}}", activity.data["id"])
642 |> assign(:valid_signature, true)
643 |> put_req_header("signature", "keyId=\"#{followed_relay.ap_id}/main-key\"")
644 |> put_req_header("content-type", "application/activity+json")
645 |> post("/inbox", accept)
646 |> json_response(200)
648 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
650 assert Pleroma.FollowingRelationship.following?(
655 Mix.shell(Mix.Shell.Process)
658 Mix.shell(Mix.Shell.IO)
661 :ok = Mix.Tasks.Pleroma.Relay.run(["list"])
662 assert_receive {:mix_shell, :info, ["https://relay.mastodon.host/actor"]}
665 @tag capture_log: true
666 test "without valid signature, " <>
667 "it only accepts Create activities and requires enabled federation",
669 data = File.read!("test/fixtures/mastodon-post-activity.json") |> Jason.decode!()
670 non_create_data = File.read!("test/fixtures/mastodon-announce.json") |> Jason.decode!()
672 conn = put_req_header(conn, "content-type", "application/activity+json")
674 clear_config([:instance, :federating], false)
677 |> post("/inbox", data)
678 |> json_response(403)
681 |> post("/inbox", non_create_data)
682 |> json_response(403)
684 clear_config([:instance, :federating], true)
686 ret_conn = post(conn, "/inbox", data)
687 assert "ok" == json_response(ret_conn, 200)
690 |> post("/inbox", non_create_data)
691 |> json_response(400)
694 test "accepts Add/Remove activities", %{conn: conn} do
695 object_id = "c61d6733-e256-4fe1-ab13-1e369789423f"
698 File.read!("test/fixtures/statuses/note.json")
699 |> String.replace("{{nickname}}", "lain")
700 |> String.replace("{{object_id}}", object_id)
702 object_url = "https://example.com/objects/#{object_id}"
705 File.read!("test/fixtures/users_mock/user.json")
706 |> String.replace("{{nickname}}", "lain")
708 actor = "https://example.com/users/lain"
712 featured_address: "https://example.com/users/lain/collections/featured"
723 headers: [{"content-type", "application/activity+json"}]
733 headers: [{"content-type", "application/activity+json"}]
736 %{method: :get, url: "https://example.com/users/lain/collections/featured"} ->
740 "test/fixtures/users_mock/masto_featured.json"
742 |> String.replace("{{domain}}", "example.com")
743 |> String.replace("{{nickname}}", "lain"),
744 headers: [{"content-type", "application/activity+json"}]
749 "id" => "https://example.com/objects/d61d6733-e256-4fe1-ab13-1e369789423f",
751 "object" => object_url,
752 "target" => "https://example.com/users/lain/collections/featured",
754 "to" => [Pleroma.Constants.as_public()]
759 |> assign(:valid_signature, true)
760 |> put_req_header("signature", "keyId=\"#{actor}/main-key\"")
761 |> put_req_header("content-type", "application/activity+json")
762 |> post("/inbox", data)
763 |> json_response(200)
765 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
766 assert Activity.get_by_ap_id(data["id"])
767 user = User.get_cached_by_ap_id(data["actor"])
769 assert user.pinned_objects[data["object"]]
772 "id" => "https://example.com/objects/d61d6733-e256-4fe1-ab13-1e369789423d",
774 "object" => object_url,
775 "target" => "https://example.com/users/lain/collections/featured",
777 "to" => [Pleroma.Constants.as_public()]
782 |> assign(:valid_signature, true)
783 |> put_req_header("signature", "keyId=\"#{actor}/main-key\"")
784 |> put_req_header("content-type", "application/activity+json")
785 |> post("/inbox", data)
786 |> json_response(200)
788 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
789 user = refresh_record(user)
790 refute user.pinned_objects[data["object"]]
793 test "mastodon pin/unpin", %{conn: conn} do
794 status_id = "105786274556060421"
797 File.read!("test/fixtures/statuses/masto-note.json")
798 |> String.replace("{{nickname}}", "lain")
799 |> String.replace("{{status_id}}", status_id)
801 status_url = "https://example.com/users/lain/statuses/#{status_id}"
802 replies_url = status_url <> "/replies?only_other_accounts=true&page=true"
805 File.read!("test/fixtures/users_mock/user.json")
806 |> String.replace("{{nickname}}", "lain")
808 actor = "https://example.com/users/lain"
813 featured_address: "https://example.com/users/lain/collections/featured"
824 headers: [{"content-type", "application/activity+json"}]
834 headers: [{"content-type", "application/activity+json"}]
837 %{method: :get, url: "https://example.com/users/lain/collections/featured"} ->
841 "test/fixtures/users_mock/masto_featured.json"
843 |> String.replace("{{domain}}", "example.com")
844 |> String.replace("{{nickname}}", "lain"),
845 headers: [{"content-type", "application/activity+json"}]
855 headers: [{"content-type", "application/activity+json"}]
860 "@context" => "https://www.w3.org/ns/activitystreams",
862 "object" => status_url,
863 "target" => "https://example.com/users/lain/collections/featured",
869 |> assign(:valid_signature, true)
870 |> put_req_header("signature", "keyId=\"#{sender.ap_id}/main-key\"")
871 |> put_req_header("content-type", "application/activity+json")
872 |> post("/inbox", data)
873 |> json_response(200)
875 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
876 assert Activity.get_by_object_ap_id_with_object(data["object"])
877 user = User.get_cached_by_ap_id(data["actor"])
878 assert user.pinned_objects[data["object"]]
882 "object" => status_url,
883 "target" => "https://example.com/users/lain/collections/featured",
889 |> assign(:valid_signature, true)
890 |> put_req_header("signature", "keyId=\"#{actor}/main-key\"")
891 |> put_req_header("content-type", "application/activity+json")
892 |> post("/inbox", data)
893 |> json_response(200)
895 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
896 assert Activity.get_by_object_ap_id_with_object(data["object"])
897 user = refresh_record(user)
898 refute user.pinned_objects[data["object"]]
902 describe "/users/:nickname/inbox" do
905 File.read!("test/fixtures/mastodon-post-activity.json")
911 test "it inserts an incoming activity into the database", %{conn: conn, data: data} do
916 |> Map.put("bcc", [user.ap_id])
917 |> Kernel.put_in(["object", "bcc"], [user.ap_id])
921 |> assign(:valid_signature, true)
922 |> put_req_header("signature", "keyId=\"#{data["actor"]}/main-key\"")
923 |> put_req_header("content-type", "application/activity+json")
924 |> post("/users/#{user.nickname}/inbox", data)
926 assert "ok" == json_response(conn, 200)
927 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
928 assert Activity.get_by_ap_id(data["id"])
931 test "it accepts messages with to as string instead of array", %{conn: conn, data: data} do
936 |> Map.put("to", user.ap_id)
938 |> Kernel.put_in(["object", "to"], user.ap_id)
939 |> Kernel.put_in(["object", "cc"], [])
943 |> assign(:valid_signature, true)
944 |> put_req_header("signature", "keyId=\"#{data["actor"]}/main-key\"")
945 |> put_req_header("content-type", "application/activity+json")
946 |> post("/users/#{user.nickname}/inbox", data)
948 assert "ok" == json_response(conn, 200)
949 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
950 assert Activity.get_by_ap_id(data["id"])
953 test "it accepts messages with cc as string instead of array", %{conn: conn, data: data} do
959 |> Map.put("cc", user.ap_id)
960 |> Kernel.put_in(["object", "to"], [])
961 |> Kernel.put_in(["object", "cc"], user.ap_id)
965 |> assign(:valid_signature, true)
966 |> put_req_header("signature", "keyId=\"#{data["actor"]}/main-key\"")
967 |> put_req_header("content-type", "application/activity+json")
968 |> post("/users/#{user.nickname}/inbox", data)
970 assert "ok" == json_response(conn, 200)
971 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
972 %Activity{} = activity = Activity.get_by_ap_id(data["id"])
973 assert user.ap_id in activity.recipients
976 test "it accepts messages with bcc as string instead of array", %{conn: conn, data: data} do
983 |> Map.put("bcc", user.ap_id)
984 |> Kernel.put_in(["object", "to"], [])
985 |> Kernel.put_in(["object", "cc"], [])
986 |> Kernel.put_in(["object", "bcc"], user.ap_id)
990 |> assign(:valid_signature, true)
991 |> put_req_header("signature", "keyId=\"#{data["actor"]}/main-key\"")
992 |> put_req_header("content-type", "application/activity+json")
993 |> post("/users/#{user.nickname}/inbox", data)
995 assert "ok" == json_response(conn, 200)
996 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
997 assert Activity.get_by_ap_id(data["id"])
1000 test "it accepts announces with to as string instead of array", %{conn: conn} do
1001 user = insert(:user)
1003 {:ok, post} = CommonAPI.post(user, %{status: "hey"})
1004 announcer = insert(:user, local: false)
1007 "@context" => "https://www.w3.org/ns/activitystreams",
1008 "actor" => announcer.ap_id,
1009 "id" => "#{announcer.ap_id}/statuses/19512778738411822/activity",
1010 "object" => post.data["object"],
1011 "to" => "https://www.w3.org/ns/activitystreams#Public",
1012 "cc" => [user.ap_id],
1013 "type" => "Announce"
1018 |> assign(:valid_signature, true)
1019 |> put_req_header("signature", "keyId=\"#{announcer.ap_id}/main-key\"")
1020 |> put_req_header("content-type", "application/activity+json")
1021 |> post("/users/#{user.nickname}/inbox", data)
1023 assert "ok" == json_response(conn, 200)
1024 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
1025 %Activity{} = activity = Activity.get_by_ap_id(data["id"])
1026 assert "https://www.w3.org/ns/activitystreams#Public" in activity.recipients
1029 test "it accepts messages from actors that are followed by the user", %{
1033 recipient = insert(:user)
1034 actor = insert(:user, %{ap_id: "http://mastodon.example.org/users/actor"})
1036 {:ok, recipient, actor} = User.follow(recipient, actor)
1040 |> Map.put("attributedTo", actor.ap_id)
1044 |> Map.put("actor", actor.ap_id)
1045 |> Map.put("object", object)
1049 |> assign(:valid_signature, true)
1050 |> put_req_header("signature", "keyId=\"#{actor.ap_id}/main-key\"")
1051 |> put_req_header("content-type", "application/activity+json")
1052 |> post("/users/#{recipient.nickname}/inbox", data)
1054 assert "ok" == json_response(conn, 200)
1055 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
1056 assert Activity.get_by_ap_id(data["id"])
1059 test "it rejects reads from other users", %{conn: conn} do
1060 user = insert(:user)
1061 other_user = insert(:user)
1065 |> assign(:user, other_user)
1066 |> put_req_header("accept", "application/activity+json")
1067 |> get("/users/#{user.nickname}/inbox")
1069 assert json_response(conn, 403)
1072 test "it returns a note activity in a collection", %{conn: conn} do
1073 note_activity = insert(:direct_note_activity)
1074 note_object = Object.normalize(note_activity, fetch: false)
1075 user = User.get_cached_by_ap_id(hd(note_activity.data["to"]))
1079 |> assign(:user, user)
1080 |> put_req_header("accept", "application/activity+json")
1081 |> get("/users/#{user.nickname}/inbox?page=true")
1083 assert response(conn, 200) =~ note_object.data["content"]
1086 test "it clears `unreachable` federation status of the sender", %{conn: conn, data: data} do
1087 user = insert(:user)
1088 data = Map.put(data, "bcc", [user.ap_id])
1090 sender_host = URI.parse(data["actor"]).host
1091 Instances.set_consistently_unreachable(sender_host)
1092 refute Instances.reachable?(sender_host)
1096 |> assign(:valid_signature, true)
1097 |> put_req_header("signature", "keyId=\"#{data["actor"]}/main-key\"")
1098 |> put_req_header("content-type", "application/activity+json")
1099 |> post("/users/#{user.nickname}/inbox", data)
1101 assert "ok" == json_response(conn, 200)
1102 assert Instances.reachable?(sender_host)
1105 @tag capture_log: true
1106 test "it removes all follower collections but actor's", %{conn: conn} do
1107 [actor, recipient] = insert_pair(:user)
1111 recipient.follower_address,
1112 "https://www.w3.org/ns/activitystreams#Public"
1115 cc = [recipient.follower_address, actor.follower_address]
1118 "@context" => ["https://www.w3.org/ns/activitystreams"],
1120 "id" => Utils.generate_activity_id(),
1123 "actor" => actor.ap_id,
1128 "content" => "It's a note",
1129 "attributedTo" => actor.ap_id,
1130 "id" => Utils.generate_object_id()
1135 |> assign(:valid_signature, true)
1136 |> put_req_header("signature", "keyId=\"#{actor.ap_id}/main-key\"")
1137 |> put_req_header("content-type", "application/activity+json")
1138 |> post("/users/#{recipient.nickname}/inbox", data)
1139 |> json_response(200)
1141 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
1143 assert activity = Activity.get_by_ap_id(data["id"])
1146 assert actor.follower_address in activity.recipients
1147 assert actor.follower_address in activity.data["cc"]
1149 refute recipient.follower_address in activity.recipients
1150 refute recipient.follower_address in activity.data["cc"]
1151 refute recipient.follower_address in activity.data["to"]
1154 test "it requires authentication", %{conn: conn} do
1155 user = insert(:user)
1156 conn = put_req_header(conn, "accept", "application/activity+json")
1158 ret_conn = get(conn, "/users/#{user.nickname}/inbox")
1159 assert json_response(ret_conn, 403)
1163 |> assign(:user, user)
1164 |> get("/users/#{user.nickname}/inbox")
1166 assert json_response(ret_conn, 200)
1169 @tag capture_log: true
1170 test "forwarded report", %{conn: conn} do
1171 admin = insert(:user, is_admin: true)
1172 actor = insert(:user, local: false)
1173 remote_domain = URI.parse(actor.ap_id).host
1174 reported_user = insert(:user)
1176 note = insert(:note_activity, user: reported_user)
1180 "https://www.w3.org/ns/activitystreams",
1181 "https://#{remote_domain}/schemas/litepub-0.1.jsonld",
1183 "@language" => "und"
1186 "actor" => actor.ap_id,
1190 "content" => "test",
1191 "context" => "context",
1192 "id" => "http://#{remote_domain}/activities/02be56cf-35e3-46b4-b2c6-47ae08dfee9e",
1193 "nickname" => reported_user.nickname,
1195 reported_user.ap_id,
1198 "actor_type" => "Person",
1199 "approval_pending" => false,
1201 "confirmation_pending" => false,
1202 "deactivated" => false,
1203 "display_name" => "test user",
1204 "id" => reported_user.id,
1206 "nickname" => reported_user.nickname,
1207 "registration_reason" => nil,
1210 "moderator" => false
1213 "url" => reported_user.ap_id
1216 "id" => note.data["id"],
1217 "published" => note.data["published"],
1221 "published" => note.data["published"],
1228 |> assign(:valid_signature, true)
1229 |> put_req_header("signature", "keyId=\"#{actor.ap_id}/main-key\"")
1230 |> put_req_header("content-type", "application/activity+json")
1231 |> post("/users/#{reported_user.nickname}/inbox", data)
1232 |> json_response(200)
1234 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
1236 assert Pleroma.Repo.aggregate(Activity, :count, :id) == 2
1238 ObanHelpers.perform_all()
1240 Swoosh.TestAssertions.assert_email_sent(
1241 to: {admin.name, admin.email},
1242 html_body: ~r/Reported Account:/i
1246 @tag capture_log: true
1247 test "forwarded report from mastodon", %{conn: conn} do
1248 admin = insert(:user, is_admin: true)
1249 actor = insert(:user, local: false)
1250 remote_domain = URI.parse(actor.ap_id).host
1251 remote_actor = "https://#{remote_domain}/actor"
1252 [reported_user, another] = insert_list(2, :user)
1254 note = insert(:note_activity, user: reported_user)
1256 Pleroma.Web.CommonAPI.favorite(another, note.id)
1259 "test/fixtures/mastodon/application_actor.json"
1261 |> String.replace("{{DOMAIN}}", remote_domain)
1263 Tesla.Mock.mock(fn %{url: ^remote_actor} ->
1266 body: mock_json_body,
1267 headers: [{"content-type", "application/activity+json"}]
1272 "@context" => "https://www.w3.org/ns/activitystreams",
1273 "actor" => remote_actor,
1274 "content" => "test report",
1275 "id" => "https://#{remote_domain}/e3b12fd1-948c-446e-b93b-a5e67edbe1d8",
1277 reported_user.ap_id,
1284 |> assign(:valid_signature, true)
1285 |> put_req_header("signature", "keyId=\"#{remote_actor}/main-key\"")
1286 |> put_req_header("content-type", "application/activity+json")
1287 |> post("/users/#{reported_user.nickname}/inbox", data)
1288 |> json_response(200)
1290 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
1292 flag_activity = "Flag" |> Pleroma.Activity.Queries.by_type() |> Pleroma.Repo.one()
1293 reported_user_ap_id = reported_user.ap_id
1295 [^reported_user_ap_id, flag_data] = flag_activity.data["object"]
1297 Enum.each(~w(actor content id published type), &Map.has_key?(flag_data, &1))
1298 ObanHelpers.perform_all()
1300 Swoosh.TestAssertions.assert_email_sent(
1301 to: {admin.name, admin.email},
1302 html_body: ~r/#{note.data["object"]}/i
1307 describe "GET /users/:nickname/outbox" do
1308 test "it paginates correctly", %{conn: conn} do
1309 user = insert(:user)
1310 conn = assign(conn, :user, user)
1311 outbox_endpoint = user.ap_id <> "/outbox"
1315 {:ok, activity} = CommonAPI.post(user, %{status: "post #{i}"})
1321 |> put_req_header("accept", "application/activity+json")
1322 |> get(outbox_endpoint <> "?page=true")
1323 |> json_response(200)
1325 result_ids = Enum.map(result["orderedItems"], fn x -> x["id"] end)
1326 assert length(result["orderedItems"]) == 20
1327 assert length(result_ids) == 20
1328 assert result["next"]
1329 assert String.starts_with?(result["next"], outbox_endpoint)
1333 |> put_req_header("accept", "application/activity+json")
1334 |> get(result["next"])
1335 |> json_response(200)
1337 result_next_ids = Enum.map(result_next["orderedItems"], fn x -> x["id"] end)
1338 assert length(result_next["orderedItems"]) == 6
1339 assert length(result_next_ids) == 6
1340 refute Enum.find(result_next_ids, fn x -> x in result_ids end)
1341 refute Enum.find(result_ids, fn x -> x in result_next_ids end)
1342 assert String.starts_with?(result["id"], outbox_endpoint)
1346 |> put_req_header("accept", "application/activity+json")
1347 |> get(result_next["id"])
1348 |> json_response(200)
1350 assert result_next == result_next_again
1353 test "it returns 200 even if there're no activities", %{conn: conn} do
1354 user = insert(:user)
1355 outbox_endpoint = user.ap_id <> "/outbox"
1359 |> assign(:user, user)
1360 |> put_req_header("accept", "application/activity+json")
1361 |> get(outbox_endpoint)
1363 result = json_response(conn, 200)
1364 assert outbox_endpoint == result["id"]
1367 test "it returns a local note activity when authenticated as local user", %{conn: conn} do
1368 user = insert(:user)
1369 reader = insert(:user)
1370 {:ok, note_activity} = CommonAPI.post(user, %{status: "mew mew", visibility: "local"})
1371 ap_id = note_activity.data["id"]
1375 |> assign(:user, reader)
1376 |> put_req_header("accept", "application/activity+json")
1377 |> get("/users/#{user.nickname}/outbox?page=true")
1378 |> json_response(200)
1380 assert %{"orderedItems" => [%{"id" => ^ap_id}]} = resp
1383 test "it does not return a local note activity when unauthenticated", %{conn: conn} do
1384 user = insert(:user)
1385 {:ok, _note_activity} = CommonAPI.post(user, %{status: "mew mew", visibility: "local"})
1389 |> put_req_header("accept", "application/activity+json")
1390 |> get("/users/#{user.nickname}/outbox?page=true")
1391 |> json_response(200)
1393 assert %{"orderedItems" => []} = resp
1396 test "it returns a note activity in a collection", %{conn: conn} do
1397 note_activity = insert(:note_activity)
1398 note_object = Object.normalize(note_activity, fetch: false)
1399 user = User.get_cached_by_ap_id(note_activity.data["actor"])
1403 |> assign(:user, user)
1404 |> put_req_header("accept", "application/activity+json")
1405 |> get("/users/#{user.nickname}/outbox?page=true")
1407 assert response(conn, 200) =~ note_object.data["content"]
1410 test "it returns an announce activity in a collection", %{conn: conn} do
1411 announce_activity = insert(:announce_activity)
1412 user = User.get_cached_by_ap_id(announce_activity.data["actor"])
1416 |> assign(:user, user)
1417 |> put_req_header("accept", "application/activity+json")
1418 |> get("/users/#{user.nickname}/outbox?page=true")
1420 assert response(conn, 200) =~ announce_activity.data["object"]
1423 test "It returns poll Answers when authenticated", %{conn: conn} do
1424 poller = insert(:user)
1425 voter = insert(:user)
1428 CommonAPI.post(poller, %{
1430 poll: %{options: ["suya", "suya.", "suya.."], expires_in: 10}
1433 assert question = Object.normalize(activity, fetch: false)
1435 {:ok, [activity], _object} = CommonAPI.vote(voter, question, [1])
1439 |> assign(:user, voter)
1440 |> put_req_header("accept", "application/activity+json")
1441 |> get(voter.ap_id <> "/outbox?page=true")
1442 |> json_response(200)
1444 assert [answer_outbox] = outbox_get["orderedItems"]
1445 assert answer_outbox["id"] == activity.data["id"]
1449 describe "POST /users/:nickname/outbox (C2S)" do
1450 setup do: clear_config([:instance, :limit])
1455 "@context" => "https://www.w3.org/ns/activitystreams",
1459 "content" => "AP C2S test",
1460 "to" => "https://www.w3.org/ns/activitystreams#Public",
1467 test "it rejects posts from other users / unauthenticated users", %{
1471 user = insert(:user)
1472 other_user = insert(:user)
1473 conn = put_req_header(conn, "content-type", "application/activity+json")
1476 |> post("/users/#{user.nickname}/outbox", activity)
1477 |> json_response(403)
1480 |> assign(:user, other_user)
1481 |> post("/users/#{user.nickname}/outbox", activity)
1482 |> json_response(403)
1485 test "it inserts an incoming create activity into the database", %{
1489 user = insert(:user)
1493 |> assign(:user, user)
1494 |> put_req_header("content-type", "application/activity+json")
1495 |> post("/users/#{user.nickname}/outbox", activity)
1496 |> json_response(201)
1498 assert Activity.get_by_ap_id(result["id"])
1499 assert result["object"]
1500 assert %Object{data: object} = Object.normalize(result["object"], fetch: false)
1501 assert object["content"] == activity["object"]["content"]
1504 test "it rejects anything beyond 'Note' creations", %{conn: conn, activity: activity} do
1505 user = insert(:user)
1509 |> put_in(["object", "type"], "Benis")
1513 |> assign(:user, user)
1514 |> put_req_header("content-type", "application/activity+json")
1515 |> post("/users/#{user.nickname}/outbox", activity)
1516 |> json_response(400)
1519 test "it inserts an incoming sensitive activity into the database", %{
1523 user = insert(:user)
1524 conn = assign(conn, :user, user)
1525 object = Map.put(activity["object"], "sensitive", true)
1526 activity = Map.put(activity, "object", object)
1530 |> put_req_header("content-type", "application/activity+json")
1531 |> post("/users/#{user.nickname}/outbox", activity)
1532 |> json_response(201)
1534 assert Activity.get_by_ap_id(response["id"])
1535 assert response["object"]
1536 assert %Object{data: response_object} = Object.normalize(response["object"], fetch: false)
1537 assert response_object["sensitive"] == true
1538 assert response_object["content"] == activity["object"]["content"]
1542 |> put_req_header("accept", "application/activity+json")
1543 |> get(response["id"])
1544 |> json_response(200)
1546 assert representation["object"]["sensitive"] == true
1549 test "it rejects an incoming activity with bogus type", %{conn: conn, activity: activity} do
1550 user = insert(:user)
1551 activity = Map.put(activity, "type", "BadType")
1555 |> assign(:user, user)
1556 |> put_req_header("content-type", "application/activity+json")
1557 |> post("/users/#{user.nickname}/outbox", activity)
1559 assert json_response(conn, 400)
1562 test "it erects a tombstone when receiving a delete activity", %{conn: conn} do
1563 note_activity = insert(:note_activity)
1564 note_object = Object.normalize(note_activity, fetch: false)
1565 user = User.get_cached_by_ap_id(note_activity.data["actor"])
1570 "id" => note_object.data["id"]
1576 |> assign(:user, user)
1577 |> put_req_header("content-type", "application/activity+json")
1578 |> post("/users/#{user.nickname}/outbox", data)
1579 |> json_response(201)
1581 assert Activity.get_by_ap_id(result["id"])
1583 assert object = Object.get_by_ap_id(note_object.data["id"])
1584 assert object.data["type"] == "Tombstone"
1587 test "it rejects delete activity of object from other actor", %{conn: conn} do
1588 note_activity = insert(:note_activity)
1589 note_object = Object.normalize(note_activity, fetch: false)
1590 user = insert(:user)
1595 id: note_object.data["id"]
1601 |> assign(:user, user)
1602 |> put_req_header("content-type", "application/activity+json")
1603 |> post("/users/#{user.nickname}/outbox", data)
1605 assert json_response(conn, 403)
1608 test "it increases like count when receiving a like action", %{conn: conn} do
1609 note_activity = insert(:note_activity)
1610 note_object = Object.normalize(note_activity, fetch: false)
1611 user = User.get_cached_by_ap_id(note_activity.data["actor"])
1616 id: note_object.data["id"]
1622 |> assign(:user, user)
1623 |> put_req_header("content-type", "application/activity+json")
1624 |> post("/users/#{user.nickname}/outbox", data)
1626 result = json_response(conn, 201)
1627 assert Activity.get_by_ap_id(result["id"])
1629 assert object = Object.get_by_ap_id(note_object.data["id"])
1630 assert object.data["like_count"] == 1
1633 test "it doesn't spreads faulty attributedTo or actor fields", %{
1637 reimu = insert(:user, nickname: "reimu")
1638 cirno = insert(:user, nickname: "cirno")
1645 |> put_in(["object", "actor"], reimu.ap_id)
1646 |> put_in(["object", "attributedTo"], reimu.ap_id)
1647 |> put_in(["actor"], reimu.ap_id)
1648 |> put_in(["attributedTo"], reimu.ap_id)
1652 |> assign(:user, cirno)
1653 |> put_req_header("content-type", "application/activity+json")
1654 |> post("/users/#{reimu.nickname}/outbox", activity)
1655 |> json_response(403)
1659 |> assign(:user, cirno)
1660 |> put_req_header("content-type", "application/activity+json")
1661 |> post("/users/#{cirno.nickname}/outbox", activity)
1662 |> json_response(201)
1664 assert cirno_outbox["attributedTo"] == nil
1665 assert cirno_outbox["actor"] == cirno.ap_id
1667 assert cirno_object = Object.normalize(cirno_outbox["object"], fetch: false)
1668 assert cirno_object.data["actor"] == cirno.ap_id
1669 assert cirno_object.data["attributedTo"] == cirno.ap_id
1672 test "Character limitation", %{conn: conn, activity: activity} do
1673 clear_config([:instance, :limit], 5)
1674 user = insert(:user)
1678 |> assign(:user, user)
1679 |> put_req_header("content-type", "application/activity+json")
1680 |> post("/users/#{user.nickname}/outbox", activity)
1681 |> json_response(400)
1683 assert result == "Character limit (5 characters) exceeded, contains 11 characters"
1687 describe "/relay/followers" do
1688 test "it returns relay followers", %{conn: conn} do
1689 relay_actor = Relay.get_actor()
1690 user = insert(:user)
1691 User.follow(user, relay_actor)
1695 |> get("/relay/followers")
1696 |> json_response(200)
1698 assert result["first"]["orderedItems"] == [user.ap_id]
1701 test "on non-federating instance, it returns 404", %{conn: conn} do
1702 clear_config([:instance, :federating], false)
1703 user = insert(:user)
1706 |> assign(:user, user)
1707 |> get("/relay/followers")
1708 |> json_response(404)
1712 describe "/relay/following" do
1713 test "it returns relay following", %{conn: conn} do
1716 |> get("/relay/following")
1717 |> json_response(200)
1719 assert result["first"]["orderedItems"] == []
1722 test "on non-federating instance, it returns 404", %{conn: conn} do
1723 clear_config([:instance, :federating], false)
1724 user = insert(:user)
1727 |> assign(:user, user)
1728 |> get("/relay/following")
1729 |> json_response(404)
1733 describe "/users/:nickname/followers" do
1734 test "it returns the followers in a collection", %{conn: conn} do
1735 user = insert(:user)
1736 user_two = insert(:user)
1737 User.follow(user, user_two)
1741 |> assign(:user, user_two)
1742 |> get("/users/#{user_two.nickname}/followers")
1743 |> json_response(200)
1745 assert result["first"]["orderedItems"] == [user.ap_id]
1748 test "it returns a uri if the user has 'hide_followers' set", %{conn: conn} do
1749 user = insert(:user)
1750 user_two = insert(:user, hide_followers: true)
1751 User.follow(user, user_two)
1755 |> assign(:user, user)
1756 |> get("/users/#{user_two.nickname}/followers")
1757 |> json_response(200)
1759 assert is_binary(result["first"])
1762 test "it returns a 403 error on pages, if the user has 'hide_followers' set and the request is from another user",
1764 user = insert(:user)
1765 other_user = insert(:user, hide_followers: true)
1769 |> assign(:user, user)
1770 |> get("/users/#{other_user.nickname}/followers?page=1")
1772 assert result.status == 403
1773 assert result.resp_body == ""
1776 test "it renders the page, if the user has 'hide_followers' set and the request is authenticated with the same user",
1778 user = insert(:user, hide_followers: true)
1779 other_user = insert(:user)
1780 {:ok, _other_user, user, _activity} = CommonAPI.follow(other_user, user)
1784 |> assign(:user, user)
1785 |> get("/users/#{user.nickname}/followers?page=1")
1786 |> json_response(200)
1788 assert result["totalItems"] == 1
1789 assert result["orderedItems"] == [other_user.ap_id]
1792 test "it works for more than 10 users", %{conn: conn} do
1793 user = insert(:user)
1795 Enum.each(1..15, fn _ ->
1796 other_user = insert(:user)
1797 User.follow(other_user, user)
1802 |> assign(:user, user)
1803 |> get("/users/#{user.nickname}/followers")
1804 |> json_response(200)
1806 assert length(result["first"]["orderedItems"]) == 10
1807 assert result["first"]["totalItems"] == 15
1808 assert result["totalItems"] == 15
1812 |> assign(:user, user)
1813 |> get("/users/#{user.nickname}/followers?page=2")
1814 |> json_response(200)
1816 assert length(result["orderedItems"]) == 5
1817 assert result["totalItems"] == 15
1820 test "does not require authentication", %{conn: conn} do
1821 user = insert(:user)
1824 |> get("/users/#{user.nickname}/followers")
1825 |> json_response(200)
1829 describe "/users/:nickname/following" do
1830 test "it returns the following in a collection", %{conn: conn} do
1831 user = insert(:user)
1832 user_two = insert(:user)
1833 User.follow(user, user_two)
1837 |> assign(:user, user)
1838 |> get("/users/#{user.nickname}/following")
1839 |> json_response(200)
1841 assert result["first"]["orderedItems"] == [user_two.ap_id]
1844 test "it returns a uri if the user has 'hide_follows' set", %{conn: conn} do
1845 user = insert(:user)
1846 user_two = insert(:user, hide_follows: true)
1847 User.follow(user, user_two)
1851 |> assign(:user, user)
1852 |> get("/users/#{user_two.nickname}/following")
1853 |> json_response(200)
1855 assert is_binary(result["first"])
1858 test "it returns a 403 error on pages, if the user has 'hide_follows' set and the request is from another user",
1860 user = insert(:user)
1861 user_two = insert(:user, hide_follows: true)
1865 |> assign(:user, user)
1866 |> get("/users/#{user_two.nickname}/following?page=1")
1868 assert result.status == 403
1869 assert result.resp_body == ""
1872 test "it renders the page, if the user has 'hide_follows' set and the request is authenticated with the same user",
1874 user = insert(:user, hide_follows: true)
1875 other_user = insert(:user)
1876 {:ok, user, _other_user, _activity} = CommonAPI.follow(user, other_user)
1880 |> assign(:user, user)
1881 |> get("/users/#{user.nickname}/following?page=1")
1882 |> json_response(200)
1884 assert result["totalItems"] == 1
1885 assert result["orderedItems"] == [other_user.ap_id]
1888 test "it works for more than 10 users", %{conn: conn} do
1889 user = insert(:user)
1891 Enum.each(1..15, fn _ ->
1892 user = User.get_cached_by_id(user.id)
1893 other_user = insert(:user)
1894 User.follow(user, other_user)
1899 |> assign(:user, user)
1900 |> get("/users/#{user.nickname}/following")
1901 |> json_response(200)
1903 assert length(result["first"]["orderedItems"]) == 10
1904 assert result["first"]["totalItems"] == 15
1905 assert result["totalItems"] == 15
1909 |> assign(:user, user)
1910 |> get("/users/#{user.nickname}/following?page=2")
1911 |> json_response(200)
1913 assert length(result["orderedItems"]) == 5
1914 assert result["totalItems"] == 15
1917 test "does not require authentication", %{conn: conn} do
1918 user = insert(:user)
1921 |> get("/users/#{user.nickname}/following")
1922 |> json_response(200)
1926 describe "delivery tracking" do
1927 test "it tracks a signed object fetch", %{conn: conn} do
1928 user = insert(:user, local: false)
1929 activity = insert(:note_activity)
1930 object = Object.normalize(activity, fetch: false)
1932 object_path = String.trim_leading(object.data["id"], Pleroma.Web.Endpoint.url())
1935 |> put_req_header("accept", "application/activity+json")
1936 |> assign(:user, user)
1938 |> json_response(200)
1940 assert Delivery.get(object.id, user.id)
1943 test "it tracks a signed activity fetch", %{conn: conn} do
1944 user = insert(:user, local: false)
1945 activity = insert(:note_activity)
1946 object = Object.normalize(activity, fetch: false)
1948 activity_path = String.trim_leading(activity.data["id"], Pleroma.Web.Endpoint.url())
1951 |> put_req_header("accept", "application/activity+json")
1952 |> assign(:user, user)
1953 |> get(activity_path)
1954 |> json_response(200)
1956 assert Delivery.get(object.id, user.id)
1959 test "it tracks a signed object fetch when the json is cached", %{conn: conn} do
1960 user = insert(:user, local: false)
1961 other_user = insert(:user, local: false)
1962 activity = insert(:note_activity)
1963 object = Object.normalize(activity, fetch: false)
1965 object_path = String.trim_leading(object.data["id"], Pleroma.Web.Endpoint.url())
1968 |> put_req_header("accept", "application/activity+json")
1969 |> assign(:user, user)
1971 |> json_response(200)
1974 |> put_req_header("accept", "application/activity+json")
1975 |> assign(:user, other_user)
1977 |> json_response(200)
1979 assert Delivery.get(object.id, user.id)
1980 assert Delivery.get(object.id, other_user.id)
1983 test "it tracks a signed activity fetch when the json is cached", %{conn: conn} do
1984 user = insert(:user, local: false)
1985 other_user = insert(:user, local: false)
1986 activity = insert(:note_activity)
1987 object = Object.normalize(activity, fetch: false)
1989 activity_path = String.trim_leading(activity.data["id"], Pleroma.Web.Endpoint.url())
1992 |> put_req_header("accept", "application/activity+json")
1993 |> assign(:user, user)
1994 |> get(activity_path)
1995 |> json_response(200)
1998 |> put_req_header("accept", "application/activity+json")
1999 |> assign(:user, other_user)
2000 |> get(activity_path)
2001 |> json_response(200)
2003 assert Delivery.get(object.id, user.id)
2004 assert Delivery.get(object.id, other_user.id)
2008 describe "Additional ActivityPub C2S endpoints" do
2009 test "GET /api/ap/whoami", %{conn: conn} do
2010 user = insert(:user)
2014 |> assign(:user, user)
2015 |> get("/api/ap/whoami")
2017 user = User.get_cached_by_id(user.id)
2019 assert UserView.render("user.json", %{user: user}) == json_response(conn, 200)
2022 |> get("/api/ap/whoami")
2023 |> json_response(403)
2026 setup do: clear_config([:media_proxy])
2027 setup do: clear_config([Pleroma.Upload])
2029 test "POST /api/ap/upload_media", %{conn: conn} do
2030 user = insert(:user)
2032 desc = "Description of the image"
2034 image = %Plug.Upload{
2035 content_type: "image/jpeg",
2036 path: Path.absname("test/fixtures/image.jpg"),
2037 filename: "an_image.jpg"
2042 |> assign(:user, user)
2043 |> post("/api/ap/upload_media", %{"file" => image, "description" => desc})
2044 |> json_response(:created)
2046 assert object["name"] == desc
2047 assert object["type"] == "Document"
2048 assert object["actor"] == user.ap_id
2049 assert [%{"href" => object_href, "mediaType" => object_mediatype}] = object["url"]
2050 assert is_binary(object_href)
2051 assert object_mediatype == "image/jpeg"
2052 assert String.ends_with?(object_href, ".jpg")
2054 activity_request = %{
2055 "@context" => "https://www.w3.org/ns/activitystreams",
2059 "content" => "AP C2S test, attachment",
2060 "attachment" => [object],
2061 "to" => "https://www.w3.org/ns/activitystreams#Public",
2068 |> assign(:user, user)
2069 |> post("/users/#{user.nickname}/outbox", activity_request)
2070 |> json_response(:created)
2072 assert activity_response["id"]
2073 assert activity_response["object"]
2074 assert activity_response["actor"] == user.ap_id
2076 assert %Object{data: %{"attachment" => [attachment]}} =
2077 Object.normalize(activity_response["object"], fetch: false)
2079 assert attachment["type"] == "Document"
2080 assert attachment["name"] == desc
2084 "href" => ^object_href,
2086 "mediaType" => ^object_mediatype
2088 ] = attachment["url"]
2090 # Fails if unauthenticated
2092 |> post("/api/ap/upload_media", %{"file" => image, "description" => desc})
2093 |> json_response(403)
2097 test "pinned collection", %{conn: conn} do
2098 clear_config([:instance, :max_pinned_statuses], 2)
2099 user = insert(:user)
2100 objects = insert_list(2, :note, user: user)
2102 Enum.reduce(objects, user, fn %{data: %{"id" => object_id}}, user ->
2103 {:ok, updated} = User.add_pinned_object_id(user, object_id)
2107 %{nickname: nickname, featured_address: featured_address, pinned_objects: pinned_objects} =
2108 refresh_record(user)
2110 %{"id" => ^featured_address, "orderedItems" => items, "totalItems" => 2} =
2112 |> get("/users/#{nickname}/collections/featured")
2113 |> json_response(200)
2115 object_ids = Enum.map(items, & &1["id"])
2117 assert Enum.all?(pinned_objects, fn {obj_id, _} ->
2118 obj_id in object_ids