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])
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 "it returns a json representation of the object with accept application/json", %{
254 uuid = String.split(note.data["id"], "/") |> List.last()
258 |> put_req_header("accept", "application/json")
259 |> get("/objects/#{uuid}")
261 assert json_response(conn, 200) == ObjectView.render("object.json", %{object: note})
264 test "it returns a json representation of the object with accept application/activity+json",
267 uuid = String.split(note.data["id"], "/") |> List.last()
271 |> put_req_header("accept", "application/activity+json")
272 |> get("/objects/#{uuid}")
274 assert json_response(conn, 200) == ObjectView.render("object.json", %{object: note})
277 test "it returns a json representation of the object with accept application/ld+json", %{
281 uuid = String.split(note.data["id"], "/") |> List.last()
287 "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\""
289 |> get("/objects/#{uuid}")
291 assert json_response(conn, 200) == ObjectView.render("object.json", %{object: note})
294 test "it returns 404 for non-public messages", %{conn: conn} do
295 note = insert(:direct_note)
296 uuid = String.split(note.data["id"], "/") |> List.last()
300 |> put_req_header("accept", "application/activity+json")
301 |> get("/objects/#{uuid}")
303 assert json_response(conn, 404)
306 test "returns visible non-public messages when authenticated", %{conn: conn} do
307 note = insert(:direct_note)
308 uuid = String.split(note.data["id"], "/") |> List.last()
309 user = User.get_by_ap_id(note.data["actor"])
310 marisa = insert(:user)
313 |> assign(:user, marisa)
314 |> put_req_header("accept", "application/activity+json")
315 |> get("/objects/#{uuid}")
316 |> json_response(404)
320 |> assign(:user, user)
321 |> put_req_header("accept", "application/activity+json")
322 |> get("/objects/#{uuid}")
323 |> json_response(200)
325 assert response == ObjectView.render("object.json", %{object: note})
328 test "it returns 404 for tombstone objects", %{conn: conn} do
329 tombstone = insert(:tombstone)
330 uuid = String.split(tombstone.data["id"], "/") |> List.last()
334 |> put_req_header("accept", "application/activity+json")
335 |> get("/objects/#{uuid}")
337 assert json_response(conn, 404)
340 test "it caches a response", %{conn: conn} do
342 uuid = String.split(note.data["id"], "/") |> List.last()
346 |> put_req_header("accept", "application/activity+json")
347 |> get("/objects/#{uuid}")
349 assert json_response(conn1, :ok)
350 assert Enum.any?(conn1.resp_headers, &(&1 == {"x-cache", "MISS from Pleroma"}))
354 |> put_req_header("accept", "application/activity+json")
355 |> get("/objects/#{uuid}")
357 assert json_response(conn1, :ok) == json_response(conn2, :ok)
358 assert Enum.any?(conn2.resp_headers, &(&1 == {"x-cache", "HIT from Pleroma"}))
361 test "cached purged after object deletion", %{conn: conn} do
363 uuid = String.split(note.data["id"], "/") |> List.last()
367 |> put_req_header("accept", "application/activity+json")
368 |> get("/objects/#{uuid}")
370 assert json_response(conn1, :ok)
371 assert Enum.any?(conn1.resp_headers, &(&1 == {"x-cache", "MISS from Pleroma"}))
377 |> put_req_header("accept", "application/activity+json")
378 |> get("/objects/#{uuid}")
380 assert "Not found" == json_response(conn2, :not_found)
384 describe "/activities/:uuid" do
385 test "it doesn't return a local-only activity", %{conn: conn} do
387 {:ok, post} = CommonAPI.post(user, %{status: "test", visibility: "local"})
389 assert Pleroma.Web.ActivityPub.Visibility.is_local_public?(post)
391 uuid = String.split(post.data["id"], "/") |> List.last()
395 |> put_req_header("accept", "application/json")
396 |> get("/activities/#{uuid}")
398 assert json_response(conn, 404)
401 test "returns local-only activities when authenticated", %{conn: conn} do
403 {:ok, post} = CommonAPI.post(user, %{status: "test", visibility: "local"})
405 assert Pleroma.Web.ActivityPub.Visibility.is_local_public?(post)
407 uuid = String.split(post.data["id"], "/") |> List.last()
411 |> assign(:user, user)
412 |> put_req_header("accept", "application/activity+json")
413 |> get("/activities/#{uuid}")
415 assert json_response(response, 200) == ObjectView.render("object.json", %{object: post})
418 test "it returns a json representation of the activity", %{conn: conn} do
419 activity = insert(:note_activity)
420 uuid = String.split(activity.data["id"], "/") |> List.last()
424 |> put_req_header("accept", "application/activity+json")
425 |> get("/activities/#{uuid}")
427 assert json_response(conn, 200) == ObjectView.render("object.json", %{object: activity})
430 test "it returns 404 for non-public activities", %{conn: conn} do
431 activity = insert(:direct_note_activity)
432 uuid = String.split(activity.data["id"], "/") |> List.last()
436 |> put_req_header("accept", "application/activity+json")
437 |> get("/activities/#{uuid}")
439 assert json_response(conn, 404)
442 test "returns visible non-public messages when authenticated", %{conn: conn} do
443 note = insert(:direct_note_activity)
444 uuid = String.split(note.data["id"], "/") |> List.last()
445 user = User.get_by_ap_id(note.data["actor"])
446 marisa = insert(:user)
449 |> assign(:user, marisa)
450 |> put_req_header("accept", "application/activity+json")
451 |> get("/activities/#{uuid}")
452 |> json_response(404)
456 |> assign(:user, user)
457 |> put_req_header("accept", "application/activity+json")
458 |> get("/activities/#{uuid}")
459 |> json_response(200)
461 assert response == ObjectView.render("object.json", %{object: note})
464 test "it caches a response", %{conn: conn} do
465 activity = insert(:note_activity)
466 uuid = String.split(activity.data["id"], "/") |> List.last()
470 |> put_req_header("accept", "application/activity+json")
471 |> get("/activities/#{uuid}")
473 assert json_response(conn1, :ok)
474 assert Enum.any?(conn1.resp_headers, &(&1 == {"x-cache", "MISS from Pleroma"}))
478 |> put_req_header("accept", "application/activity+json")
479 |> get("/activities/#{uuid}")
481 assert json_response(conn1, :ok) == json_response(conn2, :ok)
482 assert Enum.any?(conn2.resp_headers, &(&1 == {"x-cache", "HIT from Pleroma"}))
485 test "cached purged after activity deletion", %{conn: conn} do
487 {:ok, activity} = CommonAPI.post(user, %{status: "cofe"})
489 uuid = String.split(activity.data["id"], "/") |> List.last()
493 |> put_req_header("accept", "application/activity+json")
494 |> get("/activities/#{uuid}")
496 assert json_response(conn1, :ok)
497 assert Enum.any?(conn1.resp_headers, &(&1 == {"x-cache", "MISS from Pleroma"}))
499 Activity.delete_all_by_object_ap_id(activity.object.data["id"])
503 |> put_req_header("accept", "application/activity+json")
504 |> get("/activities/#{uuid}")
506 assert "Not found" == json_response(conn2, :not_found)
511 test "it inserts an incoming activity into the database", %{conn: conn} do
512 data = File.read!("test/fixtures/mastodon-post-activity.json") |> Jason.decode!()
516 |> assign(:valid_signature, true)
517 |> put_req_header("content-type", "application/activity+json")
518 |> post("/inbox", data)
520 assert "ok" == json_response(conn, 200)
522 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
523 assert Activity.get_by_ap_id(data["id"])
526 @tag capture_log: true
527 test "it inserts an incoming activity into the database" <>
528 "even if we can't fetch the user but have it in our db",
532 ap_id: "https://mastodon.example.org/users/raymoo",
535 last_refreshed_at: nil
539 File.read!("test/fixtures/mastodon-post-activity.json")
541 |> Map.put("actor", user.ap_id)
542 |> put_in(["object", "attridbutedTo"], user.ap_id)
546 |> assign(:valid_signature, true)
547 |> put_req_header("content-type", "application/activity+json")
548 |> post("/inbox", data)
550 assert "ok" == json_response(conn, 200)
552 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
553 assert Activity.get_by_ap_id(data["id"])
556 test "it clears `unreachable` federation status of the sender", %{conn: conn} do
557 data = File.read!("test/fixtures/mastodon-post-activity.json") |> Jason.decode!()
559 sender_url = data["actor"]
560 Instances.set_consistently_unreachable(sender_url)
561 refute Instances.reachable?(sender_url)
565 |> assign(:valid_signature, true)
566 |> put_req_header("content-type", "application/activity+json")
567 |> post("/inbox", data)
569 assert "ok" == json_response(conn, 200)
570 assert Instances.reachable?(sender_url)
573 test "accept follow activity", %{conn: conn} do
574 clear_config([:instance, :federating], true)
575 relay = Relay.get_actor()
577 assert {:ok, %Activity{} = activity} = Relay.follow("https://relay.mastodon.host/actor")
579 followed_relay = Pleroma.User.get_by_ap_id("https://relay.mastodon.host/actor")
580 relay = refresh_record(relay)
583 File.read!("test/fixtures/relay/accept-follow.json")
584 |> String.replace("{{ap_id}}", relay.ap_id)
585 |> String.replace("{{activity_id}}", activity.data["id"])
589 |> assign(:valid_signature, true)
590 |> put_req_header("content-type", "application/activity+json")
591 |> post("/inbox", accept)
592 |> json_response(200)
594 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
596 assert Pleroma.FollowingRelationship.following?(
601 Mix.shell(Mix.Shell.Process)
604 Mix.shell(Mix.Shell.IO)
607 :ok = Mix.Tasks.Pleroma.Relay.run(["list"])
608 assert_receive {:mix_shell, :info, ["https://relay.mastodon.host/actor"]}
611 @tag capture_log: true
612 test "without valid signature, " <>
613 "it only accepts Create activities and requires enabled federation",
615 data = File.read!("test/fixtures/mastodon-post-activity.json") |> Jason.decode!()
616 non_create_data = File.read!("test/fixtures/mastodon-announce.json") |> Jason.decode!()
618 conn = put_req_header(conn, "content-type", "application/activity+json")
620 clear_config([:instance, :federating], false)
623 |> post("/inbox", data)
624 |> json_response(403)
627 |> post("/inbox", non_create_data)
628 |> json_response(403)
630 clear_config([:instance, :federating], true)
632 ret_conn = post(conn, "/inbox", data)
633 assert "ok" == json_response(ret_conn, 200)
636 |> post("/inbox", non_create_data)
637 |> json_response(400)
640 test "accepts Add/Remove activities", %{conn: conn} do
641 object_id = "c61d6733-e256-4fe1-ab13-1e369789423f"
644 File.read!("test/fixtures/statuses/note.json")
645 |> String.replace("{{nickname}}", "lain")
646 |> String.replace("{{object_id}}", object_id)
648 object_url = "https://example.com/objects/#{object_id}"
651 File.read!("test/fixtures/users_mock/user.json")
652 |> String.replace("{{nickname}}", "lain")
654 actor = "https://example.com/users/lain"
664 headers: [{"content-type", "application/activity+json"}]
674 headers: [{"content-type", "application/activity+json"}]
677 %{method: :get, url: "https://example.com/users/lain/collections/featured"} ->
681 "test/fixtures/users_mock/masto_featured.json"
683 |> String.replace("{{domain}}", "example.com")
684 |> String.replace("{{nickname}}", "lain"),
685 headers: [{"content-type", "application/activity+json"}]
690 "id" => "https://example.com/objects/d61d6733-e256-4fe1-ab13-1e369789423f",
692 "object" => object_url,
693 "target" => "https://example.com/users/lain/collections/featured",
695 "to" => [Pleroma.Constants.as_public()]
700 |> assign(:valid_signature, true)
701 |> put_req_header("content-type", "application/activity+json")
702 |> post("/inbox", data)
703 |> json_response(200)
705 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
706 assert Activity.get_by_ap_id(data["id"])
707 user = User.get_cached_by_ap_id(data["actor"])
708 assert user.pinned_objects[data["object"]]
711 "id" => "https://example.com/objects/d61d6733-e256-4fe1-ab13-1e369789423d",
713 "object" => object_url,
714 "target" => "https://example.com/users/lain/collections/featured",
716 "to" => [Pleroma.Constants.as_public()]
721 |> assign(:valid_signature, true)
722 |> put_req_header("content-type", "application/activity+json")
723 |> post("/inbox", data)
724 |> json_response(200)
726 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
727 user = refresh_record(user)
728 refute user.pinned_objects[data["object"]]
731 test "mastodon pin/unpin", %{conn: conn} do
732 status_id = "105786274556060421"
735 File.read!("test/fixtures/statuses/masto-note.json")
736 |> String.replace("{{nickname}}", "lain")
737 |> String.replace("{{status_id}}", status_id)
739 status_url = "https://example.com/users/lain/statuses/#{status_id}"
742 File.read!("test/fixtures/users_mock/user.json")
743 |> String.replace("{{nickname}}", "lain")
745 actor = "https://example.com/users/lain"
755 headers: [{"content-type", "application/activity+json"}]
765 headers: [{"content-type", "application/activity+json"}]
768 %{method: :get, url: "https://example.com/users/lain/collections/featured"} ->
772 "test/fixtures/users_mock/masto_featured.json"
774 |> String.replace("{{domain}}", "example.com")
775 |> String.replace("{{nickname}}", "lain"),
776 headers: [{"content-type", "application/activity+json"}]
781 "@context" => "https://www.w3.org/ns/activitystreams",
783 "object" => status_url,
784 "target" => "https://example.com/users/lain/collections/featured",
790 |> assign(:valid_signature, true)
791 |> put_req_header("content-type", "application/activity+json")
792 |> post("/inbox", data)
793 |> json_response(200)
795 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
796 assert Activity.get_by_object_ap_id_with_object(data["object"])
797 user = User.get_cached_by_ap_id(data["actor"])
798 assert user.pinned_objects[data["object"]]
802 "object" => status_url,
803 "target" => "https://example.com/users/lain/collections/featured",
809 |> assign(:valid_signature, true)
810 |> put_req_header("content-type", "application/activity+json")
811 |> post("/inbox", data)
812 |> json_response(200)
814 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
815 assert Activity.get_by_object_ap_id_with_object(data["object"])
816 user = refresh_record(user)
817 refute user.pinned_objects[data["object"]]
821 describe "/users/:nickname/inbox" do
824 File.read!("test/fixtures/mastodon-post-activity.json")
830 test "it inserts an incoming activity into the database", %{conn: conn, data: data} do
832 data = Map.put(data, "bcc", [user.ap_id])
836 |> assign(:valid_signature, true)
837 |> put_req_header("content-type", "application/activity+json")
838 |> post("/users/#{user.nickname}/inbox", data)
840 assert "ok" == json_response(conn, 200)
841 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
842 assert Activity.get_by_ap_id(data["id"])
845 test "it accepts messages with to as string instead of array", %{conn: conn, data: data} do
849 Map.put(data, "to", user.ap_id)
854 |> assign(:valid_signature, true)
855 |> put_req_header("content-type", "application/activity+json")
856 |> post("/users/#{user.nickname}/inbox", data)
858 assert "ok" == json_response(conn, 200)
859 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
860 assert Activity.get_by_ap_id(data["id"])
863 test "it accepts messages with cc as string instead of array", %{conn: conn, data: data} do
867 Map.put(data, "cc", user.ap_id)
872 |> assign(:valid_signature, true)
873 |> put_req_header("content-type", "application/activity+json")
874 |> post("/users/#{user.nickname}/inbox", data)
876 assert "ok" == json_response(conn, 200)
877 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
878 %Activity{} = activity = Activity.get_by_ap_id(data["id"])
879 assert user.ap_id in activity.recipients
882 test "it accepts messages with bcc as string instead of array", %{conn: conn, data: data} do
886 Map.put(data, "bcc", user.ap_id)
892 |> assign(:valid_signature, true)
893 |> put_req_header("content-type", "application/activity+json")
894 |> post("/users/#{user.nickname}/inbox", data)
896 assert "ok" == json_response(conn, 200)
897 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
898 assert Activity.get_by_ap_id(data["id"])
901 test "it accepts announces with to as string instead of array", %{conn: conn} do
904 {:ok, post} = CommonAPI.post(user, %{status: "hey"})
905 announcer = insert(:user, local: false)
908 "@context" => "https://www.w3.org/ns/activitystreams",
909 "actor" => announcer.ap_id,
910 "id" => "#{announcer.ap_id}/statuses/19512778738411822/activity",
911 "object" => post.data["object"],
912 "to" => "https://www.w3.org/ns/activitystreams#Public",
913 "cc" => [user.ap_id],
919 |> assign(:valid_signature, true)
920 |> put_req_header("content-type", "application/activity+json")
921 |> post("/users/#{user.nickname}/inbox", data)
923 assert "ok" == json_response(conn, 200)
924 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
925 %Activity{} = activity = Activity.get_by_ap_id(data["id"])
926 assert "https://www.w3.org/ns/activitystreams#Public" in activity.recipients
929 test "it accepts messages from actors that are followed by the user", %{
933 recipient = insert(:user)
934 actor = insert(:user, %{ap_id: "http://mastodon.example.org/users/actor"})
936 {:ok, recipient, actor} = User.follow(recipient, actor)
940 |> Map.put("attributedTo", actor.ap_id)
944 |> Map.put("actor", actor.ap_id)
945 |> Map.put("object", object)
949 |> assign(:valid_signature, true)
950 |> put_req_header("content-type", "application/activity+json")
951 |> post("/users/#{recipient.nickname}/inbox", data)
953 assert "ok" == json_response(conn, 200)
954 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
955 assert Activity.get_by_ap_id(data["id"])
958 test "it rejects reads from other users", %{conn: conn} do
960 other_user = insert(:user)
964 |> assign(:user, other_user)
965 |> put_req_header("accept", "application/activity+json")
966 |> get("/users/#{user.nickname}/inbox")
968 assert json_response(conn, 403)
971 test "it returns a note activity in a collection", %{conn: conn} do
972 note_activity = insert(:direct_note_activity)
973 note_object = Object.normalize(note_activity, fetch: false)
974 user = User.get_cached_by_ap_id(hd(note_activity.data["to"]))
978 |> assign(:user, user)
979 |> put_req_header("accept", "application/activity+json")
980 |> get("/users/#{user.nickname}/inbox?page=true")
982 assert response(conn, 200) =~ note_object.data["content"]
985 test "it clears `unreachable` federation status of the sender", %{conn: conn, data: data} do
987 data = Map.put(data, "bcc", [user.ap_id])
989 sender_host = URI.parse(data["actor"]).host
990 Instances.set_consistently_unreachable(sender_host)
991 refute Instances.reachable?(sender_host)
995 |> assign(:valid_signature, true)
996 |> put_req_header("content-type", "application/activity+json")
997 |> post("/users/#{user.nickname}/inbox", data)
999 assert "ok" == json_response(conn, 200)
1000 assert Instances.reachable?(sender_host)
1003 test "it removes all follower collections but actor's", %{conn: conn} do
1004 [actor, recipient] = insert_pair(:user)
1007 File.read!("test/fixtures/activitypub-client-post-activity.json")
1010 object = Map.put(data["object"], "attributedTo", actor.ap_id)
1014 |> Map.put("id", Utils.generate_object_id())
1015 |> Map.put("actor", actor.ap_id)
1016 |> Map.put("object", object)
1018 recipient.follower_address,
1019 actor.follower_address
1023 recipient.follower_address,
1024 "https://www.w3.org/ns/activitystreams#Public"
1028 |> assign(:valid_signature, true)
1029 |> put_req_header("content-type", "application/activity+json")
1030 |> post("/users/#{recipient.nickname}/inbox", data)
1031 |> json_response(200)
1033 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
1035 activity = Activity.get_by_ap_id(data["id"])
1038 assert actor.follower_address in activity.recipients
1039 assert actor.follower_address in activity.data["cc"]
1041 refute recipient.follower_address in activity.recipients
1042 refute recipient.follower_address in activity.data["cc"]
1043 refute recipient.follower_address in activity.data["to"]
1046 test "it requires authentication", %{conn: conn} do
1047 user = insert(:user)
1048 conn = put_req_header(conn, "accept", "application/activity+json")
1050 ret_conn = get(conn, "/users/#{user.nickname}/inbox")
1051 assert json_response(ret_conn, 403)
1055 |> assign(:user, user)
1056 |> get("/users/#{user.nickname}/inbox")
1058 assert json_response(ret_conn, 200)
1061 @tag capture_log: true
1062 test "forwarded report", %{conn: conn} do
1063 admin = insert(:user, is_admin: true)
1064 actor = insert(:user, local: false)
1065 remote_domain = URI.parse(actor.ap_id).host
1066 reported_user = insert(:user)
1068 note = insert(:note_activity, user: reported_user)
1072 "https://www.w3.org/ns/activitystreams",
1073 "https://#{remote_domain}/schemas/litepub-0.1.jsonld",
1075 "@language" => "und"
1078 "actor" => actor.ap_id,
1082 "content" => "test",
1083 "context" => "context",
1084 "id" => "http://#{remote_domain}/activities/02be56cf-35e3-46b4-b2c6-47ae08dfee9e",
1085 "nickname" => reported_user.nickname,
1087 reported_user.ap_id,
1090 "actor_type" => "Person",
1091 "approval_pending" => false,
1093 "confirmation_pending" => false,
1094 "deactivated" => false,
1095 "display_name" => "test user",
1096 "id" => reported_user.id,
1098 "nickname" => reported_user.nickname,
1099 "registration_reason" => nil,
1102 "moderator" => false
1105 "url" => reported_user.ap_id
1108 "id" => note.data["id"],
1109 "published" => note.data["published"],
1113 "published" => note.data["published"],
1120 |> assign(:valid_signature, true)
1121 |> put_req_header("content-type", "application/activity+json")
1122 |> post("/users/#{reported_user.nickname}/inbox", data)
1123 |> json_response(200)
1125 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
1127 assert Pleroma.Repo.aggregate(Activity, :count, :id) == 2
1129 ObanHelpers.perform_all()
1131 Swoosh.TestAssertions.assert_email_sent(
1132 to: {admin.name, admin.email},
1133 html_body: ~r/Reported Account:/i
1137 @tag capture_log: true
1138 test "forwarded report from mastodon", %{conn: conn} do
1139 admin = insert(:user, is_admin: true)
1140 actor = insert(:user, local: false)
1141 remote_domain = URI.parse(actor.ap_id).host
1142 remote_actor = "https://#{remote_domain}/actor"
1143 [reported_user, another] = insert_list(2, :user)
1145 note = insert(:note_activity, user: reported_user)
1147 Pleroma.Web.CommonAPI.favorite(another, note.id)
1150 "test/fixtures/mastodon/application_actor.json"
1152 |> String.replace("{{DOMAIN}}", remote_domain)
1154 Tesla.Mock.mock(fn %{url: ^remote_actor} ->
1157 body: mock_json_body,
1158 headers: [{"content-type", "application/activity+json"}]
1163 "@context" => "https://www.w3.org/ns/activitystreams",
1164 "actor" => remote_actor,
1165 "content" => "test report",
1166 "id" => "https://#{remote_domain}/e3b12fd1-948c-446e-b93b-a5e67edbe1d8",
1167 "nickname" => reported_user.nickname,
1169 reported_user.ap_id,
1176 |> assign(:valid_signature, true)
1177 |> put_req_header("content-type", "application/activity+json")
1178 |> post("/users/#{reported_user.nickname}/inbox", data)
1179 |> json_response(200)
1181 ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
1183 flag_activity = "Flag" |> Pleroma.Activity.Queries.by_type() |> Pleroma.Repo.one()
1184 reported_user_ap_id = reported_user.ap_id
1186 [^reported_user_ap_id, flag_data] = flag_activity.data["object"]
1188 Enum.each(~w(actor content id published type), &Map.has_key?(flag_data, &1))
1189 ObanHelpers.perform_all()
1191 Swoosh.TestAssertions.assert_email_sent(
1192 to: {admin.name, admin.email},
1193 html_body: ~r/#{note.data["object"]}/i
1198 describe "GET /users/:nickname/outbox" do
1199 test "it paginates correctly", %{conn: conn} do
1200 user = insert(:user)
1201 conn = assign(conn, :user, user)
1202 outbox_endpoint = user.ap_id <> "/outbox"
1206 {:ok, activity} = CommonAPI.post(user, %{status: "post #{i}"})
1212 |> put_req_header("accept", "application/activity+json")
1213 |> get(outbox_endpoint <> "?page=true")
1214 |> json_response(200)
1216 result_ids = Enum.map(result["orderedItems"], fn x -> x["id"] end)
1217 assert length(result["orderedItems"]) == 20
1218 assert length(result_ids) == 20
1219 assert result["next"]
1220 assert String.starts_with?(result["next"], outbox_endpoint)
1224 |> put_req_header("accept", "application/activity+json")
1225 |> get(result["next"])
1226 |> json_response(200)
1228 result_next_ids = Enum.map(result_next["orderedItems"], fn x -> x["id"] end)
1229 assert length(result_next["orderedItems"]) == 6
1230 assert length(result_next_ids) == 6
1231 refute Enum.find(result_next_ids, fn x -> x in result_ids end)
1232 refute Enum.find(result_ids, fn x -> x in result_next_ids end)
1233 assert String.starts_with?(result["id"], outbox_endpoint)
1237 |> put_req_header("accept", "application/activity+json")
1238 |> get(result_next["id"])
1239 |> json_response(200)
1241 assert result_next == result_next_again
1244 test "it returns 200 even if there're no activities", %{conn: conn} do
1245 user = insert(:user)
1246 outbox_endpoint = user.ap_id <> "/outbox"
1250 |> assign(:user, user)
1251 |> put_req_header("accept", "application/activity+json")
1252 |> get(outbox_endpoint)
1254 result = json_response(conn, 200)
1255 assert outbox_endpoint == result["id"]
1258 test "it returns a note activity in a collection", %{conn: conn} do
1259 note_activity = insert(:note_activity)
1260 note_object = Object.normalize(note_activity, fetch: false)
1261 user = User.get_cached_by_ap_id(note_activity.data["actor"])
1265 |> assign(:user, user)
1266 |> put_req_header("accept", "application/activity+json")
1267 |> get("/users/#{user.nickname}/outbox?page=true")
1269 assert response(conn, 200) =~ note_object.data["content"]
1272 test "it returns an announce activity in a collection", %{conn: conn} do
1273 announce_activity = insert(:announce_activity)
1274 user = User.get_cached_by_ap_id(announce_activity.data["actor"])
1278 |> assign(:user, user)
1279 |> put_req_header("accept", "application/activity+json")
1280 |> get("/users/#{user.nickname}/outbox?page=true")
1282 assert response(conn, 200) =~ announce_activity.data["object"]
1285 test "It returns poll Answers when authenticated", %{conn: conn} do
1286 poller = insert(:user)
1287 voter = insert(:user)
1290 CommonAPI.post(poller, %{
1292 poll: %{options: ["suya", "suya.", "suya.."], expires_in: 10}
1295 assert question = Object.normalize(activity, fetch: false)
1297 {:ok, [activity], _object} = CommonAPI.vote(voter, question, [1])
1301 |> assign(:user, voter)
1302 |> put_req_header("accept", "application/activity+json")
1303 |> get(voter.ap_id <> "/outbox?page=true")
1304 |> json_response(200)
1306 assert [answer_outbox] = outbox_get["orderedItems"]
1307 assert answer_outbox["id"] == activity.data["id"]
1311 describe "POST /users/:nickname/outbox (C2S)" do
1312 setup do: clear_config([:instance, :limit])
1317 "@context" => "https://www.w3.org/ns/activitystreams",
1319 "object" => %{"type" => "Note", "content" => "AP C2S test"},
1320 "to" => "https://www.w3.org/ns/activitystreams#Public",
1326 test "it rejects posts from other users / unauthenticated users", %{
1330 user = insert(:user)
1331 other_user = insert(:user)
1332 conn = put_req_header(conn, "content-type", "application/activity+json")
1335 |> post("/users/#{user.nickname}/outbox", activity)
1336 |> json_response(403)
1339 |> assign(:user, other_user)
1340 |> post("/users/#{user.nickname}/outbox", activity)
1341 |> json_response(403)
1344 test "it inserts an incoming create activity into the database", %{
1348 user = insert(:user)
1352 |> assign(:user, user)
1353 |> put_req_header("content-type", "application/activity+json")
1354 |> post("/users/#{user.nickname}/outbox", activity)
1355 |> json_response(201)
1357 assert Activity.get_by_ap_id(result["id"])
1358 assert result["object"]
1359 assert %Object{data: object} = Object.normalize(result["object"], fetch: false)
1360 assert object["content"] == activity["object"]["content"]
1363 test "it rejects anything beyond 'Note' creations", %{conn: conn, activity: activity} do
1364 user = insert(:user)
1368 |> put_in(["object", "type"], "Benis")
1372 |> assign(:user, user)
1373 |> put_req_header("content-type", "application/activity+json")
1374 |> post("/users/#{user.nickname}/outbox", activity)
1375 |> json_response(400)
1378 test "it inserts an incoming sensitive activity into the database", %{
1382 user = insert(:user)
1383 conn = assign(conn, :user, user)
1384 object = Map.put(activity["object"], "sensitive", true)
1385 activity = Map.put(activity, "object", object)
1389 |> put_req_header("content-type", "application/activity+json")
1390 |> post("/users/#{user.nickname}/outbox", activity)
1391 |> json_response(201)
1393 assert Activity.get_by_ap_id(response["id"])
1394 assert response["object"]
1395 assert %Object{data: response_object} = Object.normalize(response["object"], fetch: false)
1396 assert response_object["sensitive"] == true
1397 assert response_object["content"] == activity["object"]["content"]
1401 |> put_req_header("accept", "application/activity+json")
1402 |> get(response["id"])
1403 |> json_response(200)
1405 assert representation["object"]["sensitive"] == true
1408 test "it rejects an incoming activity with bogus type", %{conn: conn, activity: activity} do
1409 user = insert(:user)
1410 activity = Map.put(activity, "type", "BadType")
1414 |> assign(:user, user)
1415 |> put_req_header("content-type", "application/activity+json")
1416 |> post("/users/#{user.nickname}/outbox", activity)
1418 assert json_response(conn, 400)
1421 test "it erects a tombstone when receiving a delete activity", %{conn: conn} do
1422 note_activity = insert(:note_activity)
1423 note_object = Object.normalize(note_activity, fetch: false)
1424 user = User.get_cached_by_ap_id(note_activity.data["actor"])
1429 id: note_object.data["id"]
1435 |> assign(:user, user)
1436 |> put_req_header("content-type", "application/activity+json")
1437 |> post("/users/#{user.nickname}/outbox", data)
1439 result = json_response(conn, 201)
1440 assert Activity.get_by_ap_id(result["id"])
1442 assert object = Object.get_by_ap_id(note_object.data["id"])
1443 assert object.data["type"] == "Tombstone"
1446 test "it rejects delete activity of object from other actor", %{conn: conn} do
1447 note_activity = insert(:note_activity)
1448 note_object = Object.normalize(note_activity, fetch: false)
1449 user = insert(:user)
1454 id: note_object.data["id"]
1460 |> assign(:user, user)
1461 |> put_req_header("content-type", "application/activity+json")
1462 |> post("/users/#{user.nickname}/outbox", data)
1464 assert json_response(conn, 400)
1467 test "it increases like count when receiving a like action", %{conn: conn} do
1468 note_activity = insert(:note_activity)
1469 note_object = Object.normalize(note_activity, fetch: false)
1470 user = User.get_cached_by_ap_id(note_activity.data["actor"])
1475 id: note_object.data["id"]
1481 |> assign(:user, user)
1482 |> put_req_header("content-type", "application/activity+json")
1483 |> post("/users/#{user.nickname}/outbox", data)
1485 result = json_response(conn, 201)
1486 assert Activity.get_by_ap_id(result["id"])
1488 assert object = Object.get_by_ap_id(note_object.data["id"])
1489 assert object.data["like_count"] == 1
1492 test "it doesn't spreads faulty attributedTo or actor fields", %{
1496 reimu = insert(:user, nickname: "reimu")
1497 cirno = insert(:user, nickname: "cirno")
1504 |> put_in(["object", "actor"], reimu.ap_id)
1505 |> put_in(["object", "attributedTo"], reimu.ap_id)
1506 |> put_in(["actor"], reimu.ap_id)
1507 |> put_in(["attributedTo"], reimu.ap_id)
1511 |> assign(:user, cirno)
1512 |> put_req_header("content-type", "application/activity+json")
1513 |> post("/users/#{reimu.nickname}/outbox", activity)
1514 |> json_response(403)
1518 |> assign(:user, cirno)
1519 |> put_req_header("content-type", "application/activity+json")
1520 |> post("/users/#{cirno.nickname}/outbox", activity)
1521 |> json_response(201)
1523 assert cirno_outbox["attributedTo"] == nil
1524 assert cirno_outbox["actor"] == cirno.ap_id
1526 assert cirno_object = Object.normalize(cirno_outbox["object"], fetch: false)
1527 assert cirno_object.data["actor"] == cirno.ap_id
1528 assert cirno_object.data["attributedTo"] == cirno.ap_id
1531 test "Character limitation", %{conn: conn, activity: activity} do
1532 clear_config([:instance, :limit], 5)
1533 user = insert(:user)
1537 |> assign(:user, user)
1538 |> put_req_header("content-type", "application/activity+json")
1539 |> post("/users/#{user.nickname}/outbox", activity)
1540 |> json_response(400)
1542 assert result == "Note is over the character limit"
1546 describe "/relay/followers" do
1547 test "it returns relay followers", %{conn: conn} do
1548 relay_actor = Relay.get_actor()
1549 user = insert(:user)
1550 User.follow(user, relay_actor)
1554 |> get("/relay/followers")
1555 |> json_response(200)
1557 assert result["first"]["orderedItems"] == [user.ap_id]
1560 test "on non-federating instance, it returns 404", %{conn: conn} do
1561 clear_config([:instance, :federating], false)
1562 user = insert(:user)
1565 |> assign(:user, user)
1566 |> get("/relay/followers")
1567 |> json_response(404)
1571 describe "/relay/following" do
1572 test "it returns relay following", %{conn: conn} do
1575 |> get("/relay/following")
1576 |> json_response(200)
1578 assert result["first"]["orderedItems"] == []
1581 test "on non-federating instance, it returns 404", %{conn: conn} do
1582 clear_config([:instance, :federating], false)
1583 user = insert(:user)
1586 |> assign(:user, user)
1587 |> get("/relay/following")
1588 |> json_response(404)
1592 describe "/users/:nickname/followers" do
1593 test "it returns the followers in a collection", %{conn: conn} do
1594 user = insert(:user)
1595 user_two = insert(:user)
1596 User.follow(user, user_two)
1600 |> assign(:user, user_two)
1601 |> get("/users/#{user_two.nickname}/followers")
1602 |> json_response(200)
1604 assert result["first"]["orderedItems"] == [user.ap_id]
1607 test "it returns a uri if the user has 'hide_followers' set", %{conn: conn} do
1608 user = insert(:user)
1609 user_two = insert(:user, hide_followers: true)
1610 User.follow(user, user_two)
1614 |> assign(:user, user)
1615 |> get("/users/#{user_two.nickname}/followers")
1616 |> json_response(200)
1618 assert is_binary(result["first"])
1621 test "it returns a 403 error on pages, if the user has 'hide_followers' set and the request is from another user",
1623 user = insert(:user)
1624 other_user = insert(:user, hide_followers: true)
1628 |> assign(:user, user)
1629 |> get("/users/#{other_user.nickname}/followers?page=1")
1631 assert result.status == 403
1632 assert result.resp_body == ""
1635 test "it renders the page, if the user has 'hide_followers' set and the request is authenticated with the same user",
1637 user = insert(:user, hide_followers: true)
1638 other_user = insert(:user)
1639 {:ok, _other_user, user, _activity} = CommonAPI.follow(other_user, user)
1643 |> assign(:user, user)
1644 |> get("/users/#{user.nickname}/followers?page=1")
1645 |> json_response(200)
1647 assert result["totalItems"] == 1
1648 assert result["orderedItems"] == [other_user.ap_id]
1651 test "it works for more than 10 users", %{conn: conn} do
1652 user = insert(:user)
1654 Enum.each(1..15, fn _ ->
1655 other_user = insert(:user)
1656 User.follow(other_user, user)
1661 |> assign(:user, user)
1662 |> get("/users/#{user.nickname}/followers")
1663 |> json_response(200)
1665 assert length(result["first"]["orderedItems"]) == 10
1666 assert result["first"]["totalItems"] == 15
1667 assert result["totalItems"] == 15
1671 |> assign(:user, user)
1672 |> get("/users/#{user.nickname}/followers?page=2")
1673 |> json_response(200)
1675 assert length(result["orderedItems"]) == 5
1676 assert result["totalItems"] == 15
1679 test "does not require authentication", %{conn: conn} do
1680 user = insert(:user)
1683 |> get("/users/#{user.nickname}/followers")
1684 |> json_response(200)
1688 describe "/users/:nickname/following" do
1689 test "it returns the following in a collection", %{conn: conn} do
1690 user = insert(:user)
1691 user_two = insert(:user)
1692 User.follow(user, user_two)
1696 |> assign(:user, user)
1697 |> get("/users/#{user.nickname}/following")
1698 |> json_response(200)
1700 assert result["first"]["orderedItems"] == [user_two.ap_id]
1703 test "it returns a uri if the user has 'hide_follows' set", %{conn: conn} do
1704 user = insert(:user)
1705 user_two = insert(:user, hide_follows: true)
1706 User.follow(user, user_two)
1710 |> assign(:user, user)
1711 |> get("/users/#{user_two.nickname}/following")
1712 |> json_response(200)
1714 assert is_binary(result["first"])
1717 test "it returns a 403 error on pages, if the user has 'hide_follows' set and the request is from another user",
1719 user = insert(:user)
1720 user_two = insert(:user, hide_follows: true)
1724 |> assign(:user, user)
1725 |> get("/users/#{user_two.nickname}/following?page=1")
1727 assert result.status == 403
1728 assert result.resp_body == ""
1731 test "it renders the page, if the user has 'hide_follows' set and the request is authenticated with the same user",
1733 user = insert(:user, hide_follows: true)
1734 other_user = insert(:user)
1735 {:ok, user, _other_user, _activity} = CommonAPI.follow(user, other_user)
1739 |> assign(:user, user)
1740 |> get("/users/#{user.nickname}/following?page=1")
1741 |> json_response(200)
1743 assert result["totalItems"] == 1
1744 assert result["orderedItems"] == [other_user.ap_id]
1747 test "it works for more than 10 users", %{conn: conn} do
1748 user = insert(:user)
1750 Enum.each(1..15, fn _ ->
1751 user = User.get_cached_by_id(user.id)
1752 other_user = insert(:user)
1753 User.follow(user, other_user)
1758 |> assign(:user, user)
1759 |> get("/users/#{user.nickname}/following")
1760 |> json_response(200)
1762 assert length(result["first"]["orderedItems"]) == 10
1763 assert result["first"]["totalItems"] == 15
1764 assert result["totalItems"] == 15
1768 |> assign(:user, user)
1769 |> get("/users/#{user.nickname}/following?page=2")
1770 |> json_response(200)
1772 assert length(result["orderedItems"]) == 5
1773 assert result["totalItems"] == 15
1776 test "does not require authentication", %{conn: conn} do
1777 user = insert(:user)
1780 |> get("/users/#{user.nickname}/following")
1781 |> json_response(200)
1785 describe "delivery tracking" do
1786 test "it tracks a signed object fetch", %{conn: conn} do
1787 user = insert(:user, local: false)
1788 activity = insert(:note_activity)
1789 object = Object.normalize(activity, fetch: false)
1791 object_path = String.trim_leading(object.data["id"], Pleroma.Web.Endpoint.url())
1794 |> put_req_header("accept", "application/activity+json")
1795 |> assign(:user, user)
1797 |> json_response(200)
1799 assert Delivery.get(object.id, user.id)
1802 test "it tracks a signed activity fetch", %{conn: conn} do
1803 user = insert(:user, local: false)
1804 activity = insert(:note_activity)
1805 object = Object.normalize(activity, fetch: false)
1807 activity_path = String.trim_leading(activity.data["id"], Pleroma.Web.Endpoint.url())
1810 |> put_req_header("accept", "application/activity+json")
1811 |> assign(:user, user)
1812 |> get(activity_path)
1813 |> json_response(200)
1815 assert Delivery.get(object.id, user.id)
1818 test "it tracks a signed object fetch when the json is cached", %{conn: conn} do
1819 user = insert(:user, local: false)
1820 other_user = insert(:user, local: false)
1821 activity = insert(:note_activity)
1822 object = Object.normalize(activity, fetch: false)
1824 object_path = String.trim_leading(object.data["id"], Pleroma.Web.Endpoint.url())
1827 |> put_req_header("accept", "application/activity+json")
1828 |> assign(:user, user)
1830 |> json_response(200)
1833 |> put_req_header("accept", "application/activity+json")
1834 |> assign(:user, other_user)
1836 |> json_response(200)
1838 assert Delivery.get(object.id, user.id)
1839 assert Delivery.get(object.id, other_user.id)
1842 test "it tracks a signed activity fetch when the json is cached", %{conn: conn} do
1843 user = insert(:user, local: false)
1844 other_user = insert(:user, local: false)
1845 activity = insert(:note_activity)
1846 object = Object.normalize(activity, fetch: false)
1848 activity_path = String.trim_leading(activity.data["id"], Pleroma.Web.Endpoint.url())
1851 |> put_req_header("accept", "application/activity+json")
1852 |> assign(:user, user)
1853 |> get(activity_path)
1854 |> json_response(200)
1857 |> put_req_header("accept", "application/activity+json")
1858 |> assign(:user, other_user)
1859 |> get(activity_path)
1860 |> json_response(200)
1862 assert Delivery.get(object.id, user.id)
1863 assert Delivery.get(object.id, other_user.id)
1867 describe "Additional ActivityPub C2S endpoints" do
1868 test "GET /api/ap/whoami", %{conn: conn} do
1869 user = insert(:user)
1873 |> assign(:user, user)
1874 |> get("/api/ap/whoami")
1876 user = User.get_cached_by_id(user.id)
1878 assert UserView.render("user.json", %{user: user}) == json_response(conn, 200)
1881 |> get("/api/ap/whoami")
1882 |> json_response(403)
1885 setup do: clear_config([:media_proxy])
1886 setup do: clear_config([Pleroma.Upload])
1888 test "POST /api/ap/upload_media", %{conn: conn} do
1889 user = insert(:user)
1891 desc = "Description of the image"
1893 image = %Plug.Upload{
1894 content_type: "image/jpeg",
1895 path: Path.absname("test/fixtures/image.jpg"),
1896 filename: "an_image.jpg"
1901 |> assign(:user, user)
1902 |> post("/api/ap/upload_media", %{"file" => image, "description" => desc})
1903 |> json_response(:created)
1905 assert object["name"] == desc
1906 assert object["type"] == "Document"
1907 assert object["actor"] == user.ap_id
1908 assert [%{"href" => object_href, "mediaType" => object_mediatype}] = object["url"]
1909 assert is_binary(object_href)
1910 assert object_mediatype == "image/jpeg"
1911 assert String.ends_with?(object_href, ".jpg")
1913 activity_request = %{
1914 "@context" => "https://www.w3.org/ns/activitystreams",
1918 "content" => "AP C2S test, attachment",
1919 "attachment" => [object]
1921 "to" => "https://www.w3.org/ns/activitystreams#Public",
1927 |> assign(:user, user)
1928 |> post("/users/#{user.nickname}/outbox", activity_request)
1929 |> json_response(:created)
1931 assert activity_response["id"]
1932 assert activity_response["object"]
1933 assert activity_response["actor"] == user.ap_id
1935 assert %Object{data: %{"attachment" => [attachment]}} =
1936 Object.normalize(activity_response["object"], fetch: false)
1938 assert attachment["type"] == "Document"
1939 assert attachment["name"] == desc
1943 "href" => ^object_href,
1945 "mediaType" => ^object_mediatype
1947 ] = attachment["url"]
1949 # Fails if unauthenticated
1951 |> post("/api/ap/upload_media", %{"file" => image, "description" => desc})
1952 |> json_response(403)
1956 test "pinned collection", %{conn: conn} do
1957 clear_config([:instance, :max_pinned_statuses], 2)
1958 user = insert(:user)
1959 objects = insert_list(2, :note, user: user)
1961 Enum.reduce(objects, user, fn %{data: %{"id" => object_id}}, user ->
1962 {:ok, updated} = User.add_pinned_object_id(user, object_id)
1966 %{nickname: nickname, featured_address: featured_address, pinned_objects: pinned_objects} =
1967 refresh_record(user)
1969 %{"id" => ^featured_address, "orderedItems" => items, "totalItems" => 2} =
1971 |> get("/users/#{nickname}/collections/featured")
1972 |> json_response(200)
1974 object_ids = Enum.map(items, & &1["id"])
1976 assert Enum.all?(pinned_objects, fn {obj_id, _} ->
1977 obj_id in object_ids