diff options
Diffstat (limited to 'test/web/activity_pub')
38 files changed, 2752 insertions, 1092 deletions
diff --git a/test/web/activity_pub/activity_pub_controller_test.exs b/test/web/activity_pub/activity_pub_controller_test.exs index ba2ce1dd9..c432c90e3 100644 --- a/test/web/activity_pub/activity_pub_controller_test.exs +++ b/test/web/activity_pub/activity_pub_controller_test.exs @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do @@ -8,6 +8,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do import Pleroma.Factory alias Pleroma.Activity + alias Pleroma.Config alias Pleroma.Delivery alias Pleroma.Instances alias Pleroma.Object @@ -25,12 +26,10 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do :ok end - clear_config_all([:instance, :federating], - do: Pleroma.Config.put([:instance, :federating], true) - ) + setup do: clear_config([:instance, :federating], true) describe "/relay" do - clear_config([:instance, :allow_relay]) + setup do: clear_config([:instance, :allow_relay]) test "with the relay active, it returns the relay user", %{conn: conn} do res = @@ -42,12 +41,21 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do end test "with the relay disabled, it returns 404", %{conn: conn} do - Pleroma.Config.put([:instance, :allow_relay], false) + Config.put([:instance, :allow_relay], false) conn |> get(activity_pub_path(conn, :relay)) |> json_response(404) - |> assert + end + + test "on non-federating instance, it returns 404", %{conn: conn} do + Config.put([:instance, :federating], false) + user = insert(:user) + + conn + |> assign(:user, user) + |> get(activity_pub_path(conn, :relay)) + |> json_response(404) end end @@ -60,6 +68,16 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do assert res["id"] =~ "/fetch" end + + test "on non-federating instance, it returns 404", %{conn: conn} do + Config.put([:instance, :federating], false) + user = insert(:user) + + conn + |> assign(:user, user) + |> get(activity_pub_path(conn, :internal_fetch)) + |> json_response(404) + end end describe "/users/:nickname" do @@ -123,9 +141,34 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do assert json_response(conn, 404) end + + test "it returns error when user is not found", %{conn: conn} do + response = + conn + |> put_req_header("accept", "application/json") + |> get("/users/jimm") + |> json_response(404) + + assert response == "Not found" + end + + test "it requires authentication if instance is NOT federating", %{ + conn: conn + } do + user = insert(:user) + + conn = + put_req_header( + conn, + "accept", + "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"" + ) + + ensure_federating_or_authenticated(conn, "/users/#{user.nickname}.json", user) + end end - describe "/object/:uuid" do + describe "/objects/:uuid" do test "it returns a json representation of the object with accept application/json", %{ conn: conn } do @@ -236,6 +279,18 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do assert "Not found" == json_response(conn2, :not_found) end + + test "it requires authentication if instance is NOT federating", %{ + conn: conn + } do + user = insert(:user) + note = insert(:note) + uuid = String.split(note.data["id"], "/") |> List.last() + + conn = put_req_header(conn, "accept", "application/activity+json") + + ensure_federating_or_authenticated(conn, "/objects/#{uuid}", user) + end end describe "/activities/:uuid" do @@ -286,7 +341,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do test "cached purged after activity deletion", %{conn: conn} do user = insert(:user) - {:ok, activity} = CommonAPI.post(user, %{"status" => "cofe"}) + {:ok, activity} = CommonAPI.post(user, %{status: "cofe"}) uuid = String.split(activity.data["id"], "/") |> List.last() @@ -307,6 +362,18 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do assert "Not found" == json_response(conn2, :not_found) end + + test "it requires authentication if instance is NOT federating", %{ + conn: conn + } do + user = insert(:user) + activity = insert(:note_activity) + uuid = String.split(activity.data["id"], "/") |> List.last() + + conn = put_req_header(conn, "accept", "application/activity+json") + + ensure_federating_or_authenticated(conn, "/activities/#{uuid}", user) + end end describe "/inbox" do @@ -341,6 +408,72 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do assert "ok" == json_response(conn, 200) assert Instances.reachable?(sender_url) end + + test "accept follow activity", %{conn: conn} do + Pleroma.Config.put([:instance, :federating], true) + relay = Relay.get_actor() + + assert {:ok, %Activity{} = activity} = Relay.follow("https://relay.mastodon.host/actor") + + followed_relay = Pleroma.User.get_by_ap_id("https://relay.mastodon.host/actor") + relay = refresh_record(relay) + + accept = + File.read!("test/fixtures/relay/accept-follow.json") + |> String.replace("{{ap_id}}", relay.ap_id) + |> String.replace("{{activity_id}}", activity.data["id"]) + + assert "ok" == + conn + |> assign(:valid_signature, true) + |> put_req_header("content-type", "application/activity+json") + |> post("/inbox", accept) + |> json_response(200) + + ObanHelpers.perform(all_enqueued(worker: ReceiverWorker)) + + assert Pleroma.FollowingRelationship.following?( + relay, + followed_relay + ) + + Mix.shell(Mix.Shell.Process) + + on_exit(fn -> + Mix.shell(Mix.Shell.IO) + end) + + :ok = Mix.Tasks.Pleroma.Relay.run(["list"]) + assert_receive {:mix_shell, :info, ["relay.mastodon.host"]} + end + + test "without valid signature, " <> + "it only accepts Create activities and requires enabled federation", + %{conn: conn} do + data = File.read!("test/fixtures/mastodon-post-activity.json") |> Poison.decode!() + non_create_data = File.read!("test/fixtures/mastodon-announce.json") |> Poison.decode!() + + conn = put_req_header(conn, "content-type", "application/activity+json") + + Config.put([:instance, :federating], false) + + conn + |> post("/inbox", data) + |> json_response(403) + + conn + |> post("/inbox", non_create_data) + |> json_response(403) + + Config.put([:instance, :federating], true) + + ret_conn = post(conn, "/inbox", data) + assert "ok" == json_response(ret_conn, 200) + + conn + |> post("/inbox", non_create_data) + |> json_response(400) + end end describe "/users/:nickname/inbox" do @@ -479,22 +612,11 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do test "it rejects reads from other users", %{conn: conn} do user = insert(:user) - otheruser = insert(:user) - - conn = - conn - |> assign(:user, otheruser) - |> put_req_header("accept", "application/activity+json") - |> get("/users/#{user.nickname}/inbox") - - assert json_response(conn, 403) - end - - test "it doesn't crash without an authenticated user", %{conn: conn} do - user = insert(:user) + other_user = insert(:user) conn = conn + |> assign(:user, other_user) |> put_req_header("accept", "application/activity+json") |> get("/users/#{user.nickname}/inbox") @@ -575,14 +697,30 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do refute recipient.follower_address in activity.data["cc"] refute recipient.follower_address in activity.data["to"] end + + test "it requires authentication", %{conn: conn} do + user = insert(:user) + conn = put_req_header(conn, "accept", "application/activity+json") + + ret_conn = get(conn, "/users/#{user.nickname}/inbox") + assert json_response(ret_conn, 403) + + ret_conn = + conn + |> assign(:user, user) + |> get("/users/#{user.nickname}/inbox") + + assert json_response(ret_conn, 200) + end end - describe "/users/:nickname/outbox" do - test "it will not bomb when there is no activity", %{conn: conn} do + describe "GET /users/:nickname/outbox" do + test "it returns 200 even if there're no activities", %{conn: conn} do user = insert(:user) conn = conn + |> assign(:user, user) |> put_req_header("accept", "application/activity+json") |> get("/users/#{user.nickname}/outbox") @@ -597,6 +735,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do conn = conn + |> assign(:user, user) |> put_req_header("accept", "application/activity+json") |> get("/users/#{user.nickname}/outbox?page=true") @@ -609,54 +748,127 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do conn = conn + |> assign(:user, user) |> put_req_header("accept", "application/activity+json") |> get("/users/#{user.nickname}/outbox?page=true") assert response(conn, 200) =~ announce_activity.data["object"] end - test "it rejects posts from other users", %{conn: conn} do - data = File.read!("test/fixtures/activitypub-client-post-activity.json") |> Poison.decode!() + test "it requires authentication if instance is NOT federating", %{ + conn: conn + } do user = insert(:user) - otheruser = insert(:user) + conn = put_req_header(conn, "accept", "application/activity+json") - conn = + ensure_federating_or_authenticated(conn, "/users/#{user.nickname}/outbox", user) + end + end + + describe "POST /users/:nickname/outbox (C2S)" do + setup do + [ + activity: %{ + "@context" => "https://www.w3.org/ns/activitystreams", + "type" => "Create", + "object" => %{"type" => "Note", "content" => "AP C2S test"}, + "to" => "https://www.w3.org/ns/activitystreams#Public", + "cc" => [] + } + ] + end + + test "it rejects posts from other users / unauthenticated users", %{ + conn: conn, + activity: activity + } do + user = insert(:user) + other_user = insert(:user) + conn = put_req_header(conn, "content-type", "application/activity+json") + + conn + |> post("/users/#{user.nickname}/outbox", activity) + |> json_response(403) + + conn + |> assign(:user, other_user) + |> post("/users/#{user.nickname}/outbox", activity) + |> json_response(403) + end + + test "it inserts an incoming create activity into the database", %{ + conn: conn, + activity: activity + } do + user = insert(:user) + + result = conn - |> assign(:user, otheruser) + |> assign(:user, user) |> put_req_header("content-type", "application/activity+json") - |> post("/users/#{user.nickname}/outbox", data) + |> post("/users/#{user.nickname}/outbox", activity) + |> json_response(201) - assert json_response(conn, 403) + assert Activity.get_by_ap_id(result["id"]) + assert result["object"] + assert %Object{data: object} = Object.normalize(result["object"]) + assert object["content"] == activity["object"]["content"] end - test "it inserts an incoming create activity into the database", %{conn: conn} do - data = File.read!("test/fixtures/activitypub-client-post-activity.json") |> Poison.decode!() + test "it rejects anything beyond 'Note' creations", %{conn: conn, activity: activity} do user = insert(:user) - conn = + activity = + activity + |> put_in(["object", "type"], "Benis") + + _result = conn |> assign(:user, user) |> put_req_header("content-type", "application/activity+json") - |> post("/users/#{user.nickname}/outbox", data) + |> post("/users/#{user.nickname}/outbox", activity) + |> json_response(400) + end - result = json_response(conn, 201) + test "it inserts an incoming sensitive activity into the database", %{ + conn: conn, + activity: activity + } do + user = insert(:user) + conn = assign(conn, :user, user) + object = Map.put(activity["object"], "sensitive", true) + activity = Map.put(activity, "object", object) - assert Activity.get_by_ap_id(result["id"]) + response = + conn + |> put_req_header("content-type", "application/activity+json") + |> post("/users/#{user.nickname}/outbox", activity) + |> json_response(201) + + assert Activity.get_by_ap_id(response["id"]) + assert response["object"] + assert %Object{data: response_object} = Object.normalize(response["object"]) + assert response_object["sensitive"] == true + assert response_object["content"] == activity["object"]["content"] + + representation = + conn + |> put_req_header("accept", "application/activity+json") + |> get(response["id"]) + |> json_response(200) + + assert representation["object"]["sensitive"] == true end - test "it rejects an incoming activity with bogus type", %{conn: conn} do - data = File.read!("test/fixtures/activitypub-client-post-activity.json") |> Poison.decode!() + test "it rejects an incoming activity with bogus type", %{conn: conn, activity: activity} do user = insert(:user) - - data = - data - |> Map.put("type", "BadType") + activity = Map.put(activity, "type", "BadType") conn = conn |> assign(:user, user) |> put_req_header("content-type", "application/activity+json") - |> post("/users/#{user.nickname}/outbox", data) + |> post("/users/#{user.nickname}/outbox", activity) assert json_response(conn, 400) end @@ -741,24 +953,42 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do result = conn - |> assign(:relay, true) |> get("/relay/followers") |> json_response(200) assert result["first"]["orderedItems"] == [user.ap_id] end + + test "on non-federating instance, it returns 404", %{conn: conn} do + Config.put([:instance, :federating], false) + user = insert(:user) + + conn + |> assign(:user, user) + |> get("/relay/followers") + |> json_response(404) + end end describe "/relay/following" do test "it returns relay following", %{conn: conn} do result = conn - |> assign(:relay, true) |> get("/relay/following") |> json_response(200) assert result["first"]["orderedItems"] == [] end + + test "on non-federating instance, it returns 404", %{conn: conn} do + Config.put([:instance, :federating], false) + user = insert(:user) + + conn + |> assign(:user, user) + |> get("/relay/following") + |> json_response(404) + end end describe "/users/:nickname/followers" do @@ -769,32 +999,36 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do result = conn + |> assign(:user, user_two) |> get("/users/#{user_two.nickname}/followers") |> json_response(200) assert result["first"]["orderedItems"] == [user.ap_id] end - test "it returns returns a uri if the user has 'hide_followers' set", %{conn: conn} do + test "it returns a uri if the user has 'hide_followers' set", %{conn: conn} do user = insert(:user) user_two = insert(:user, hide_followers: true) User.follow(user, user_two) result = conn + |> assign(:user, user) |> get("/users/#{user_two.nickname}/followers") |> json_response(200) assert is_binary(result["first"]) end - test "it returns a 403 error on pages, if the user has 'hide_followers' set and the request is not authenticated", + test "it returns a 403 error on pages, if the user has 'hide_followers' set and the request is from another user", %{conn: conn} do - user = insert(:user, hide_followers: true) + user = insert(:user) + other_user = insert(:user, hide_followers: true) result = conn - |> get("/users/#{user.nickname}/followers?page=1") + |> assign(:user, user) + |> get("/users/#{other_user.nickname}/followers?page=1") assert result.status == 403 assert result.resp_body == "" @@ -826,6 +1060,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do result = conn + |> assign(:user, user) |> get("/users/#{user.nickname}/followers") |> json_response(200) @@ -835,12 +1070,21 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do result = conn + |> assign(:user, user) |> get("/users/#{user.nickname}/followers?page=2") |> json_response(200) assert length(result["orderedItems"]) == 5 assert result["totalItems"] == 15 end + + test "does not require authentication", %{conn: conn} do + user = insert(:user) + + conn + |> get("/users/#{user.nickname}/followers") + |> json_response(200) + end end describe "/users/:nickname/following" do @@ -851,6 +1095,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do result = conn + |> assign(:user, user) |> get("/users/#{user.nickname}/following") |> json_response(200) @@ -858,25 +1103,28 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do end test "it returns a uri if the user has 'hide_follows' set", %{conn: conn} do - user = insert(:user, hide_follows: true) - user_two = insert(:user) + user = insert(:user) + user_two = insert(:user, hide_follows: true) User.follow(user, user_two) result = conn - |> get("/users/#{user.nickname}/following") + |> assign(:user, user) + |> get("/users/#{user_two.nickname}/following") |> json_response(200) assert is_binary(result["first"]) end - test "it returns a 403 error on pages, if the user has 'hide_follows' set and the request is not authenticated", + test "it returns a 403 error on pages, if the user has 'hide_follows' set and the request is from another user", %{conn: conn} do - user = insert(:user, hide_follows: true) + user = insert(:user) + user_two = insert(:user, hide_follows: true) result = conn - |> get("/users/#{user.nickname}/following?page=1") + |> assign(:user, user) + |> get("/users/#{user_two.nickname}/following?page=1") assert result.status == 403 assert result.resp_body == "" @@ -909,6 +1157,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do result = conn + |> assign(:user, user) |> get("/users/#{user.nickname}/following") |> json_response(200) @@ -918,12 +1167,21 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do result = conn + |> assign(:user, user) |> get("/users/#{user.nickname}/following?page=2") |> json_response(200) assert length(result["orderedItems"]) == 5 assert result["totalItems"] == 15 end + + test "does not require authentication", %{conn: conn} do + user = insert(:user) + + conn + |> get("/users/#{user.nickname}/following") + |> json_response(200) + end end describe "delivery tracking" do @@ -1008,8 +1266,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do end end - describe "Additionnal ActivityPub C2S endpoints" do - test "/api/ap/whoami", %{conn: conn} do + describe "Additional ActivityPub C2S endpoints" do + test "GET /api/ap/whoami", %{conn: conn} do user = insert(:user) conn = @@ -1020,12 +1278,16 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do user = User.get_cached_by_id(user.id) assert UserView.render("user.json", %{user: user}) == json_response(conn, 200) + + conn + |> get("/api/ap/whoami") + |> json_response(403) end - clear_config([:media_proxy]) - clear_config([Pleroma.Upload]) + setup do: clear_config([:media_proxy]) + setup do: clear_config([Pleroma.Upload]) - test "uploadMedia", %{conn: conn} do + test "POST /api/ap/upload_media", %{conn: conn} do user = insert(:user) desc = "Description of the image" @@ -1036,15 +1298,59 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do filename: "an_image.jpg" } - conn = + object = conn |> assign(:user, user) |> post("/api/ap/upload_media", %{"file" => image, "description" => desc}) + |> json_response(:created) - assert object = json_response(conn, :created) assert object["name"] == desc assert object["type"] == "Document" assert object["actor"] == user.ap_id + assert [%{"href" => object_href, "mediaType" => object_mediatype}] = object["url"] + assert is_binary(object_href) + assert object_mediatype == "image/jpeg" + + activity_request = %{ + "@context" => "https://www.w3.org/ns/activitystreams", + "type" => "Create", + "object" => %{ + "type" => "Note", + "content" => "AP C2S test, attachment", + "attachment" => [object] + }, + "to" => "https://www.w3.org/ns/activitystreams#Public", + "cc" => [] + } + + activity_response = + conn + |> assign(:user, user) + |> post("/users/#{user.nickname}/outbox", activity_request) + |> json_response(:created) + + assert activity_response["id"] + assert activity_response["object"] + assert activity_response["actor"] == user.ap_id + + assert %Object{data: %{"attachment" => [attachment]}} = + Object.normalize(activity_response["object"]) + + assert attachment["type"] == "Document" + assert attachment["name"] == desc + + assert [ + %{ + "href" => ^object_href, + "type" => "Link", + "mediaType" => ^object_mediatype + } + ] = attachment["url"] + + # Fails if unauthenticated + conn + |> post("/api/ap/upload_media", %{"file" => image, "description" => desc}) + |> json_response(403) end end end diff --git a/test/web/activity_pub/activity_pub_test.exs b/test/web/activity_pub/activity_pub_test.exs index 9b7cfee63..77bd07edf 100644 --- a/test/web/activity_pub/activity_pub_test.exs +++ b/test/web/activity_pub/activity_pub_test.exs @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.ActivityPub.ActivityPubTest do @@ -8,6 +8,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do alias Pleroma.Activity alias Pleroma.Builders.ActivityBuilder + alias Pleroma.Config alias Pleroma.Notification alias Pleroma.Object alias Pleroma.User @@ -16,21 +17,22 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do alias Pleroma.Web.AdminAPI.AccountView alias Pleroma.Web.CommonAPI + import ExUnit.CaptureLog + import Mock import Pleroma.Factory import Tesla.Mock - import Mock setup do mock(fn env -> apply(HttpRequestMock, :request, [env]) end) :ok end - clear_config([:instance, :federating]) + setup do: clear_config([:instance, :federating]) describe "streaming out participations" do test "it streams them out" do user = insert(:user) - {:ok, activity} = CommonAPI.post(user, %{"status" => ".", "visibility" => "direct"}) + {:ok, activity} = CommonAPI.post(user, %{status: ".", visibility: "direct"}) {:ok, conversation} = Pleroma.Conversation.create_or_bump_for(activity) @@ -54,8 +56,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do stream: fn _, _ -> nil end do {:ok, activity} = CommonAPI.post(user_one, %{ - "status" => "@#{user_two.nickname}", - "visibility" => "direct" + status: "@#{user_two.nickname}", + visibility: "direct" }) conversation = @@ -72,15 +74,13 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do test "it restricts by the appropriate visibility" do user = insert(:user) - {:ok, public_activity} = CommonAPI.post(user, %{"status" => ".", "visibility" => "public"}) + {:ok, public_activity} = CommonAPI.post(user, %{status: ".", visibility: "public"}) - {:ok, direct_activity} = CommonAPI.post(user, %{"status" => ".", "visibility" => "direct"}) + {:ok, direct_activity} = CommonAPI.post(user, %{status: ".", visibility: "direct"}) - {:ok, unlisted_activity} = - CommonAPI.post(user, %{"status" => ".", "visibility" => "unlisted"}) + {:ok, unlisted_activity} = CommonAPI.post(user, %{status: ".", visibility: "unlisted"}) - {:ok, private_activity} = - CommonAPI.post(user, %{"status" => ".", "visibility" => "private"}) + {:ok, private_activity} = CommonAPI.post(user, %{status: ".", visibility: "private"}) activities = ActivityPub.fetch_activities([], %{:visibility => "direct", "actor_id" => user.ap_id}) @@ -116,15 +116,13 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do test "it excludes by the appropriate visibility" do user = insert(:user) - {:ok, public_activity} = CommonAPI.post(user, %{"status" => ".", "visibility" => "public"}) + {:ok, public_activity} = CommonAPI.post(user, %{status: ".", visibility: "public"}) - {:ok, direct_activity} = CommonAPI.post(user, %{"status" => ".", "visibility" => "direct"}) + {:ok, direct_activity} = CommonAPI.post(user, %{status: ".", visibility: "direct"}) - {:ok, unlisted_activity} = - CommonAPI.post(user, %{"status" => ".", "visibility" => "unlisted"}) + {:ok, unlisted_activity} = CommonAPI.post(user, %{status: ".", visibility: "unlisted"}) - {:ok, private_activity} = - CommonAPI.post(user, %{"status" => ".", "visibility" => "private"}) + {:ok, private_activity} = CommonAPI.post(user, %{status: ".", visibility: "private"}) activities = ActivityPub.fetch_activities([], %{ @@ -178,7 +176,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do {:ok, user} = ActivityPub.make_user_from_ap_id(user_id) assert user.ap_id == user_id assert user.nickname == "admin@mastodon.example.org" - assert user.source_data assert user.ap_enabled assert user.follower_address == "http://mastodon.example.org/users/admin/followers" end @@ -192,9 +189,9 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do test "it fetches the appropriate tag-restricted posts" do user = insert(:user) - {:ok, status_one} = CommonAPI.post(user, %{"status" => ". #test"}) - {:ok, status_two} = CommonAPI.post(user, %{"status" => ". #essais"}) - {:ok, status_three} = CommonAPI.post(user, %{"status" => ". #test #reject"}) + {:ok, status_one} = CommonAPI.post(user, %{status: ". #test"}) + {:ok, status_two} = CommonAPI.post(user, %{status: ". #essais"}) + {:ok, status_three} = CommonAPI.post(user, %{status: ". #test #reject"}) fetch_one = ActivityPub.fetch_activities([], %{"type" => "Create", "tag" => "test"}) @@ -224,7 +221,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do describe "insertion" do test "drops activities beyond a certain limit" do - limit = Pleroma.Config.get([:instance, :remote_limit]) + limit = Config.get([:instance, :remote_limit]) random_text = :crypto.strong_rand_bytes(limit + 1) @@ -385,6 +382,27 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do end describe "create activities" do + test "it reverts create" do + user = insert(:user) + + with_mock(Utils, [:passthrough], maybe_federate: fn _ -> {:error, :reverted} end) do + assert {:error, :reverted} = + ActivityPub.create(%{ + to: ["user1", "user2"], + actor: user, + context: "", + object: %{ + "to" => ["user1", "user2"], + "type" => "Note", + "content" => "testing" + } + }) + end + + assert Repo.aggregate(Activity, :count, :id) == 0 + assert Repo.aggregate(Object, :count, :id) == 0 + end + test "removes doubled 'to' recipients" do user = insert(:user) @@ -410,26 +428,26 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do {:ok, _} = CommonAPI.post(User.get_cached_by_id(user.id), %{ - "status" => "1", - "visibility" => "public" + status: "1", + visibility: "public" }) {:ok, _} = CommonAPI.post(User.get_cached_by_id(user.id), %{ - "status" => "2", - "visibility" => "unlisted" + status: "2", + visibility: "unlisted" }) {:ok, _} = CommonAPI.post(User.get_cached_by_id(user.id), %{ - "status" => "2", - "visibility" => "private" + status: "2", + visibility: "private" }) {:ok, _} = CommonAPI.post(User.get_cached_by_id(user.id), %{ - "status" => "3", - "visibility" => "direct" + status: "3", + visibility: "direct" }) user = User.get_cached_by_id(user.id) @@ -440,27 +458,27 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do user = insert(:user) user2 = insert(:user) - {:ok, activity} = CommonAPI.post(user, %{"status" => "1", "visibility" => "public"}) + {:ok, activity} = CommonAPI.post(user, %{status: "1", visibility: "public"}) ap_id = activity.data["id"] - reply_data = %{"status" => "1", "in_reply_to_status_id" => activity.id} + reply_data = %{status: "1", in_reply_to_status_id: activity.id} # public - {:ok, _} = CommonAPI.post(user2, Map.put(reply_data, "visibility", "public")) + {:ok, _} = CommonAPI.post(user2, Map.put(reply_data, :visibility, "public")) assert %{data: data, object: object} = Activity.get_by_ap_id_with_object(ap_id) assert object.data["repliesCount"] == 1 # unlisted - {:ok, _} = CommonAPI.post(user2, Map.put(reply_data, "visibility", "unlisted")) + {:ok, _} = CommonAPI.post(user2, Map.put(reply_data, :visibility, "unlisted")) assert %{data: data, object: object} = Activity.get_by_ap_id_with_object(ap_id) assert object.data["repliesCount"] == 2 # private - {:ok, _} = CommonAPI.post(user2, Map.put(reply_data, "visibility", "private")) + {:ok, _} = CommonAPI.post(user2, Map.put(reply_data, :visibility, "private")) assert %{data: data, object: object} = Activity.get_by_ap_id_with_object(ap_id) assert object.data["repliesCount"] == 2 # direct - {:ok, _} = CommonAPI.post(user2, Map.put(reply_data, "visibility", "direct")) + {:ok, _} = CommonAPI.post(user2, Map.put(reply_data, :visibility, "direct")) assert %{data: data, object: object} = Activity.get_by_ap_id_with_object(ap_id) assert object.data["repliesCount"] == 2 end @@ -547,13 +565,13 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do {:ok, _user_relationship} = User.block(blocker, blockee) - {:ok, activity_one} = CommonAPI.post(friend, %{"status" => "hey!"}) + {:ok, activity_one} = CommonAPI.post(friend, %{status: "hey!"}) - {:ok, activity_two} = CommonAPI.post(friend, %{"status" => "hey! @#{blockee.nickname}"}) + {:ok, activity_two} = CommonAPI.post(friend, %{status: "hey! @#{blockee.nickname}"}) - {:ok, activity_three} = CommonAPI.post(blockee, %{"status" => "hey! @#{friend.nickname}"}) + {:ok, activity_three} = CommonAPI.post(blockee, %{status: "hey! @#{friend.nickname}"}) - {:ok, activity_four} = CommonAPI.post(blockee, %{"status" => "hey! @#{blocker.nickname}"}) + {:ok, activity_four} = CommonAPI.post(blockee, %{status: "hey! @#{blocker.nickname}"}) activities = ActivityPub.fetch_activities([], %{"blocking_user" => blocker}) @@ -570,9 +588,9 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do {:ok, _user_relationship} = User.block(blocker, blockee) - {:ok, activity_one} = CommonAPI.post(friend, %{"status" => "hey!"}) + {:ok, activity_one} = CommonAPI.post(friend, %{status: "hey!"}) - {:ok, activity_two} = CommonAPI.post(blockee, %{"status" => "hey! @#{friend.nickname}"}) + {:ok, activity_two} = CommonAPI.post(blockee, %{status: "hey! @#{friend.nickname}"}) {:ok, activity_three, _} = CommonAPI.repeat(activity_two.id, friend) @@ -752,10 +770,9 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do test "doesn't retrieve unlisted activities" do user = insert(:user) - {:ok, _unlisted_activity} = - CommonAPI.post(user, %{"status" => "yeah", "visibility" => "unlisted"}) + {:ok, _unlisted_activity} = CommonAPI.post(user, %{status: "yeah", visibility: "unlisted"}) - {:ok, listed_activity} = CommonAPI.post(user, %{"status" => "yeah"}) + {:ok, listed_activity} = CommonAPI.post(user, %{status: "yeah"}) [activity] = ActivityPub.fetch_public_activities() @@ -851,187 +868,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do end end - describe "react to an object" do - test_with_mock "sends an activity to federation", Pleroma.Web.Federator, [:passthrough], [] do - Pleroma.Config.put([:instance, :federating], true) - user = insert(:user) - reactor = insert(:user) - {:ok, activity} = CommonAPI.post(user, %{"status" => "YASSSS queen slay"}) - assert object = Object.normalize(activity) - - {:ok, reaction_activity, _object} = ActivityPub.react_with_emoji(reactor, object, "🔥") - - assert called(Pleroma.Web.Federator.publish(reaction_activity)) - end - - test "adds an emoji reaction activity to the db" do - user = insert(:user) - reactor = insert(:user) - third_user = insert(:user) - fourth_user = insert(:user) - {:ok, activity} = CommonAPI.post(user, %{"status" => "YASSSS queen slay"}) - assert object = Object.normalize(activity) - - {:ok, reaction_activity, object} = ActivityPub.react_with_emoji(reactor, object, "🔥") - - assert reaction_activity - - assert reaction_activity.data["actor"] == reactor.ap_id - assert reaction_activity.data["type"] == "EmojiReact" - assert reaction_activity.data["content"] == "🔥" - assert reaction_activity.data["object"] == object.data["id"] - assert reaction_activity.data["to"] == [User.ap_followers(reactor), activity.data["actor"]] - assert reaction_activity.data["context"] == object.data["context"] - assert object.data["reaction_count"] == 1 - assert object.data["reactions"] == [["🔥", [reactor.ap_id]]] - - {:ok, _reaction_activity, object} = ActivityPub.react_with_emoji(third_user, object, "☕") - - assert object.data["reaction_count"] == 2 - assert object.data["reactions"] == [["🔥", [reactor.ap_id]], ["☕", [third_user.ap_id]]] - - {:ok, _reaction_activity, object} = ActivityPub.react_with_emoji(fourth_user, object, "🔥") - - assert object.data["reaction_count"] == 3 - - assert object.data["reactions"] == [ - ["🔥", [fourth_user.ap_id, reactor.ap_id]], - ["☕", [third_user.ap_id]] - ] - end - end - - describe "unreacting to an object" do - test_with_mock "sends an activity to federation", Pleroma.Web.Federator, [:passthrough], [] do - Pleroma.Config.put([:instance, :federating], true) - user = insert(:user) - reactor = insert(:user) - {:ok, activity} = CommonAPI.post(user, %{"status" => "YASSSS queen slay"}) - assert object = Object.normalize(activity) - - {:ok, reaction_activity, _object} = ActivityPub.react_with_emoji(reactor, object, "🔥") - - assert called(Pleroma.Web.Federator.publish(reaction_activity)) - - {:ok, unreaction_activity, _object} = - ActivityPub.unreact_with_emoji(reactor, reaction_activity.data["id"]) - - assert called(Pleroma.Web.Federator.publish(unreaction_activity)) - end - - test "adds an undo activity to the db" do - user = insert(:user) - reactor = insert(:user) - {:ok, activity} = CommonAPI.post(user, %{"status" => "YASSSS queen slay"}) - assert object = Object.normalize(activity) - - {:ok, reaction_activity, _object} = ActivityPub.react_with_emoji(reactor, object, "🔥") - - {:ok, unreaction_activity, _object} = - ActivityPub.unreact_with_emoji(reactor, reaction_activity.data["id"]) - - assert unreaction_activity.actor == reactor.ap_id - assert unreaction_activity.data["object"] == reaction_activity.data["id"] - - object = Object.get_by_ap_id(object.data["id"]) - assert object.data["reaction_count"] == 0 - assert object.data["reactions"] == [] - end - end - - describe "like an object" do - test_with_mock "sends an activity to federation", Pleroma.Web.Federator, [:passthrough], [] do - Pleroma.Config.put([:instance, :federating], true) - note_activity = insert(:note_activity) - assert object_activity = Object.normalize(note_activity) - - user = insert(:user) - - {:ok, like_activity, _object} = ActivityPub.like(user, object_activity) - assert called(Pleroma.Web.Federator.publish(like_activity)) - end - - test "returns exist activity if object already liked" do - note_activity = insert(:note_activity) - assert object_activity = Object.normalize(note_activity) - - user = insert(:user) - - {:ok, like_activity, _object} = ActivityPub.like(user, object_activity) - - {:ok, like_activity_exist, _object} = ActivityPub.like(user, object_activity) - assert like_activity == like_activity_exist - end - - test "adds a like activity to the db" do - note_activity = insert(:note_activity) - assert object = Object.normalize(note_activity) - - user = insert(:user) - user_two = insert(:user) - - {:ok, like_activity, object} = ActivityPub.like(user, object) - - assert like_activity.data["actor"] == user.ap_id - assert like_activity.data["type"] == "Like" - assert like_activity.data["object"] == object.data["id"] - assert like_activity.data["to"] == [User.ap_followers(user), note_activity.data["actor"]] - assert like_activity.data["context"] == object.data["context"] - assert object.data["like_count"] == 1 - assert object.data["likes"] == [user.ap_id] - - # Just return the original activity if the user already liked it. - {:ok, same_like_activity, object} = ActivityPub.like(user, object) - - assert like_activity == same_like_activity - assert object.data["likes"] == [user.ap_id] - assert object.data["like_count"] == 1 - - {:ok, _like_activity, object} = ActivityPub.like(user_two, object) - assert object.data["like_count"] == 2 - end - end - - describe "unliking" do - test_with_mock "sends an activity to federation", Pleroma.Web.Federator, [:passthrough], [] do - Pleroma.Config.put([:instance, :federating], true) - - note_activity = insert(:note_activity) - object = Object.normalize(note_activity) - user = insert(:user) - - {:ok, object} = ActivityPub.unlike(user, object) - refute called(Pleroma.Web.Federator.publish()) - - {:ok, _like_activity, object} = ActivityPub.like(user, object) - assert object.data["like_count"] == 1 - - {:ok, unlike_activity, _, object} = ActivityPub.unlike(user, object) - assert object.data["like_count"] == 0 - - assert called(Pleroma.Web.Federator.publish(unlike_activity)) - end - - test "unliking a previously liked object" do - note_activity = insert(:note_activity) - object = Object.normalize(note_activity) - user = insert(:user) - - # Unliking something that hasn't been liked does nothing - {:ok, object} = ActivityPub.unlike(user, object) - assert object.data["like_count"] == 0 - - {:ok, like_activity, object} = ActivityPub.like(user, object) - assert object.data["like_count"] == 1 - - {:ok, unlike_activity, _, object} = ActivityPub.unlike(user, object) - assert object.data["like_count"] == 0 - - assert Activity.get_by_id(like_activity.id) == nil - assert note_activity.actor in unlike_activity.recipients - end - end - describe "announcing an object" do test "adds an announce activity to the db" do note_activity = insert(:note_activity) @@ -1051,12 +887,27 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do assert announce_activity.data["actor"] == user.ap_id assert announce_activity.data["context"] == object.data["context"] end + + test "reverts annouce from object on error" do + note_activity = insert(:note_activity) + object = Object.normalize(note_activity) + user = insert(:user) + + with_mock(Utils, [:passthrough], maybe_federate: fn _ -> {:error, :reverted} end) do + assert {:error, :reverted} = ActivityPub.announce(user, object) + end + + reloaded_object = Object.get_by_ap_id(object.data["id"]) + assert reloaded_object == object + refute reloaded_object.data["announcement_count"] + refute reloaded_object.data["announcements"] + end end describe "announcing a private object" do test "adds an announce activity to the db if the audience is not widened" do user = insert(:user) - {:ok, note_activity} = CommonAPI.post(user, %{"status" => ".", "visibility" => "private"}) + {:ok, note_activity} = CommonAPI.post(user, %{status: ".", visibility: "private"}) object = Object.normalize(note_activity) {:ok, announce_activity, object} = ActivityPub.announce(user, object, nil, true, false) @@ -1070,7 +921,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do test "does not add an announce activity to the db if the audience is widened" do user = insert(:user) - {:ok, note_activity} = CommonAPI.post(user, %{"status" => ".", "visibility" => "private"}) + {:ok, note_activity} = CommonAPI.post(user, %{status: ".", visibility: "private"}) object = Object.normalize(note_activity) assert {:error, _} = ActivityPub.announce(user, object, nil, true, true) @@ -1079,43 +930,13 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do test "does not add an announce activity to the db if the announcer is not the author" do user = insert(:user) announcer = insert(:user) - {:ok, note_activity} = CommonAPI.post(user, %{"status" => ".", "visibility" => "private"}) + {:ok, note_activity} = CommonAPI.post(user, %{status: ".", visibility: "private"}) object = Object.normalize(note_activity) assert {:error, _} = ActivityPub.announce(announcer, object, nil, true, false) end end - describe "unannouncing an object" do - test "unannouncing a previously announced object" do - note_activity = insert(:note_activity) - object = Object.normalize(note_activity) - user = insert(:user) - - # Unannouncing an object that is not announced does nothing - # {:ok, object} = ActivityPub.unannounce(user, object) - # assert object.data["announcement_count"] == 0 - - {:ok, announce_activity, object} = ActivityPub.announce(user, object) - assert object.data["announcement_count"] == 1 - - {:ok, unannounce_activity, object} = ActivityPub.unannounce(user, object) - assert object.data["announcement_count"] == 0 - - assert unannounce_activity.data["to"] == [ - User.ap_followers(user), - object.data["actor"] - ] - - assert unannounce_activity.data["type"] == "Undo" - assert unannounce_activity.data["object"] == announce_activity.data - assert unannounce_activity.data["actor"] == user.ap_id - assert unannounce_activity.data["context"] == announce_activity.data["context"] - - assert Activity.get_by_id(announce_activity.id) == nil - end - end - describe "uploading files" do test "copies the file to the configured folder" do file = %Plug.Upload{ @@ -1130,7 +951,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do test "works with base64 encoded images" do file = %{ - "img" => data_uri() + img: data_uri() } {:ok, %Object{}} = ActivityPub.upload(file) @@ -1148,6 +969,35 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do end describe "following / unfollowing" do + test "it reverts follow activity" do + follower = insert(:user) + followed = insert(:user) + + with_mock(Utils, [:passthrough], maybe_federate: fn _ -> {:error, :reverted} end) do + assert {:error, :reverted} = ActivityPub.follow(follower, followed) + end + + assert Repo.aggregate(Activity, :count, :id) == 0 + assert Repo.aggregate(Object, :count, :id) == 0 + end + + test "it reverts unfollow activity" do + follower = insert(:user) + followed = insert(:user) + + {:ok, follow_activity} = ActivityPub.follow(follower, followed) + + with_mock(Utils, [:passthrough], maybe_federate: fn _ -> {:error, :reverted} end) do + assert {:error, :reverted} = ActivityPub.unfollow(follower, followed) + end + + activity = Activity.get_by_id(follow_activity.id) + assert activity.data["type"] == "Follow" + assert activity.data["actor"] == follower.ap_id + + assert activity.data["object"] == followed.ap_id + end + test "creates a follow activity" do follower = insert(:user) followed = insert(:user) @@ -1193,151 +1043,51 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do end end - describe "blocking / unblocking" do - test "creates a block activity" do - blocker = insert(:user) - blocked = insert(:user) + describe "blocking" do + test "reverts block activity on error" do + [blocker, blocked] = insert_list(2, :user) - {:ok, activity} = ActivityPub.block(blocker, blocked) + with_mock(Utils, [:passthrough], maybe_federate: fn _ -> {:error, :reverted} end) do + assert {:error, :reverted} = ActivityPub.block(blocker, blocked) + end - assert activity.data["type"] == "Block" - assert activity.data["actor"] == blocker.ap_id - assert activity.data["object"] == blocked.ap_id + assert Repo.aggregate(Activity, :count, :id) == 0 + assert Repo.aggregate(Object, :count, :id) == 0 end - test "creates an undo activity for the last block" do + test "creates a block activity" do + clear_config([:instance, :federating], true) blocker = insert(:user) blocked = insert(:user) - {:ok, block_activity} = ActivityPub.block(blocker, blocked) - {:ok, activity} = ActivityPub.unblock(blocker, blocked) + with_mock Pleroma.Web.Federator, + publish: fn _ -> nil end do + {:ok, activity} = ActivityPub.block(blocker, blocked) - assert activity.data["type"] == "Undo" - assert activity.data["actor"] == blocker.ap_id - - embedded_object = activity.data["object"] - assert is_map(embedded_object) - assert embedded_object["type"] == "Block" - assert embedded_object["object"] == blocked.ap_id - assert embedded_object["id"] == block_activity.data["id"] - end - end - - describe "deletion" do - clear_config([:instance, :rewrite_policy]) - - test "it creates a delete activity and deletes the original object" do - note = insert(:note_activity) - object = Object.normalize(note) - {:ok, delete} = ActivityPub.delete(object) - - assert delete.data["type"] == "Delete" - assert delete.data["actor"] == note.data["actor"] - assert delete.data["object"] == object.data["id"] - - assert Activity.get_by_id(delete.id) != nil - - assert Repo.get(Object, object.id).data["type"] == "Tombstone" - end - - test "decrements user note count only for public activities" do - user = insert(:user, note_count: 10) - - {:ok, a1} = - CommonAPI.post(User.get_cached_by_id(user.id), %{ - "status" => "yeah", - "visibility" => "public" - }) - - {:ok, a2} = - CommonAPI.post(User.get_cached_by_id(user.id), %{ - "status" => "yeah", - "visibility" => "unlisted" - }) - - {:ok, a3} = - CommonAPI.post(User.get_cached_by_id(user.id), %{ - "status" => "yeah", - "visibility" => "private" - }) - - {:ok, a4} = - CommonAPI.post(User.get_cached_by_id(user.id), %{ - "status" => "yeah", - "visibility" => "direct" - }) - - {:ok, _} = Object.normalize(a1) |> ActivityPub.delete() - {:ok, _} = Object.normalize(a2) |> ActivityPub.delete() - {:ok, _} = Object.normalize(a3) |> ActivityPub.delete() - {:ok, _} = Object.normalize(a4) |> ActivityPub.delete() - - user = User.get_cached_by_id(user.id) - assert user.note_count == 10 - end - - test "it creates a delete activity and checks that it is also sent to users mentioned by the deleted object" do - user = insert(:user) - note = insert(:note_activity) - object = Object.normalize(note) - - {:ok, object} = - object - |> Object.change(%{ - data: %{ - "actor" => object.data["actor"], - "id" => object.data["id"], - "to" => [user.ap_id], - "type" => "Note" - } - }) - |> Object.update_and_set_cache() - - {:ok, delete} = ActivityPub.delete(object) - - assert user.ap_id in delete.data["to"] - end - - test "decreases reply count" do - user = insert(:user) - user2 = insert(:user) - - {:ok, activity} = CommonAPI.post(user, %{"status" => "1", "visibility" => "public"}) - reply_data = %{"status" => "1", "in_reply_to_status_id" => activity.id} - ap_id = activity.data["id"] - - {:ok, public_reply} = CommonAPI.post(user2, Map.put(reply_data, "visibility", "public")) - {:ok, unlisted_reply} = CommonAPI.post(user2, Map.put(reply_data, "visibility", "unlisted")) - {:ok, private_reply} = CommonAPI.post(user2, Map.put(reply_data, "visibility", "private")) - {:ok, direct_reply} = CommonAPI.post(user2, Map.put(reply_data, "visibility", "direct")) - - _ = CommonAPI.delete(direct_reply.id, user2) - assert %{data: data, object: object} = Activity.get_by_ap_id_with_object(ap_id) - assert object.data["repliesCount"] == 2 + assert activity.data["type"] == "Block" + assert activity.data["actor"] == blocker.ap_id + assert activity.data["object"] == blocked.ap_id - _ = CommonAPI.delete(private_reply.id, user2) - assert %{data: data, object: object} = Activity.get_by_ap_id_with_object(ap_id) - assert object.data["repliesCount"] == 2 - - _ = CommonAPI.delete(public_reply.id, user2) - assert %{data: data, object: object} = Activity.get_by_ap_id_with_object(ap_id) - assert object.data["repliesCount"] == 1 - - _ = CommonAPI.delete(unlisted_reply.id, user2) - assert %{data: data, object: object} = Activity.get_by_ap_id_with_object(ap_id) - assert object.data["repliesCount"] == 0 + assert called(Pleroma.Web.Federator.publish(activity)) + end end - test "it passes delete activity through MRF before deleting the object" do - Pleroma.Config.put([:instance, :rewrite_policy], Pleroma.Web.ActivityPub.MRF.DropPolicy) + test "works with outgoing blocks disabled, but doesn't federate" do + clear_config([:instance, :federating], true) + clear_config([:activitypub, :outgoing_blocks], false) + blocker = insert(:user) + blocked = insert(:user) - note = insert(:note_activity) - object = Object.normalize(note) + with_mock Pleroma.Web.Federator, + publish: fn _ -> nil end do + {:ok, activity} = ActivityPub.block(blocker, blocked) - {:error, {:reject, _}} = ActivityPub.delete(object) + assert activity.data["type"] == "Block" + assert activity.data["actor"] == blocker.ap_id + assert activity.data["object"] == blocked.ap_id - assert Activity.get_by_id(note.id) - assert Repo.get(Object, object.id).data["type"] == object.data["type"] + refute called(Pleroma.Web.Federator.publish(:_)) + end end end @@ -1356,23 +1106,22 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do {:ok, user3} = User.follow(user3, user2) assert User.following?(user3, user2) - {:ok, public_activity} = CommonAPI.post(user3, %{"status" => "hi 1"}) + {:ok, public_activity} = CommonAPI.post(user3, %{status: "hi 1"}) - {:ok, private_activity_1} = - CommonAPI.post(user3, %{"status" => "hi 2", "visibility" => "private"}) + {:ok, private_activity_1} = CommonAPI.post(user3, %{status: "hi 2", visibility: "private"}) {:ok, private_activity_2} = CommonAPI.post(user2, %{ - "status" => "hi 3", - "visibility" => "private", - "in_reply_to_status_id" => private_activity_1.id + status: "hi 3", + visibility: "private", + in_reply_to_status_id: private_activity_1.id }) {:ok, private_activity_3} = CommonAPI.post(user3, %{ - "status" => "hi 4", - "visibility" => "private", - "in_reply_to_status_id" => private_activity_2.id + status: "hi 4", + visibility: "private", + in_reply_to_status_id: private_activity_2.id }) activities = @@ -1395,7 +1144,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do end describe "update" do - clear_config([:instance, :max_pinned_statuses]) + setup do: clear_config([:instance, :max_pinned_statuses]) test "it creates an update activity with the new user data" do user = insert(:user) @@ -1419,12 +1168,12 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do end test "returned pinned statuses" do - Pleroma.Config.put([:instance, :max_pinned_statuses], 3) + Config.put([:instance, :max_pinned_statuses], 3) user = insert(:user) - {:ok, activity_one} = CommonAPI.post(user, %{"status" => "HI!!!"}) - {:ok, activity_two} = CommonAPI.post(user, %{"status" => "HI!!!"}) - {:ok, activity_three} = CommonAPI.post(user, %{"status" => "HI!!!"}) + {:ok, activity_one} = CommonAPI.post(user, %{status: "HI!!!"}) + {:ok, activity_two} = CommonAPI.post(user, %{status: "HI!!!"}) + {:ok, activity_three} = CommonAPI.post(user, %{status: "HI!!!"}) CommonAPI.pin(activity_one.id, user) user = refresh_record(user) @@ -1445,7 +1194,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do reporter = insert(:user) target_account = insert(:user) content = "foobar" - {:ok, activity} = CommonAPI.post(target_account, %{"status" => content}) + {:ok, activity} = CommonAPI.post(target_account, %{status: content}) context = Utils.generate_context_id() reporter_ap_id = reporter.ap_id @@ -1541,8 +1290,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do {:ok, list} = Pleroma.List.create("foo", user) {:ok, list} = Pleroma.List.follow(list, member) - {:ok, activity} = - CommonAPI.post(user, %{"status" => "foobar", "visibility" => "list:#{list.id}"}) + {:ok, activity} = CommonAPI.post(user, %{status: "foobar", visibility: "list:#{list.id}"}) activity = Repo.preload(activity, :bookmark) activity = %Activity{activity | thread_muted?: !!activity.thread_muted?} @@ -1560,8 +1308,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do {:ok, activity} = CommonAPI.post(user, %{ - "status" => "thought I looked cute might delete later :3", - "visibility" => "private" + status: "thought I looked cute might delete later :3", + visibility: "private" }) [result] = ActivityPub.fetch_activities_bounded([user.follower_address], []) @@ -1570,12 +1318,12 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do test "fetches only public posts for other users" do user = insert(:user) - {:ok, activity} = CommonAPI.post(user, %{"status" => "#cofe", "visibility" => "public"}) + {:ok, activity} = CommonAPI.post(user, %{status: "#cofe", visibility: "public"}) {:ok, _private_activity} = CommonAPI.post(user, %{ - "status" => "why is tenshi eating a corndog so cute?", - "visibility" => "private" + status: "why is tenshi eating a corndog so cute?", + visibility: "private" }) [result] = ActivityPub.fetch_activities_bounded([], [user.follower_address]) @@ -1703,20 +1451,20 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do other_user = insert(:user) user1 = insert(:user) user2 = insert(:user) - {:ok, a1} = CommonAPI.post(user1, %{"status" => "bla"}) - {:ok, _a2} = CommonAPI.post(user2, %{"status" => "traps are happy"}) - {:ok, a3} = CommonAPI.post(user2, %{"status" => "Trees Are "}) - {:ok, a4} = CommonAPI.post(user2, %{"status" => "Agent Smith "}) - {:ok, a5} = CommonAPI.post(user1, %{"status" => "Red or Blue "}) - - {:ok, _, _} = CommonAPI.favorite(a4.id, user) - {:ok, _, _} = CommonAPI.favorite(a3.id, other_user) - {:ok, _, _} = CommonAPI.favorite(a3.id, user) - {:ok, _, _} = CommonAPI.favorite(a5.id, other_user) - {:ok, _, _} = CommonAPI.favorite(a5.id, user) - {:ok, _, _} = CommonAPI.favorite(a4.id, other_user) - {:ok, _, _} = CommonAPI.favorite(a1.id, user) - {:ok, _, _} = CommonAPI.favorite(a1.id, other_user) + {:ok, a1} = CommonAPI.post(user1, %{status: "bla"}) + {:ok, _a2} = CommonAPI.post(user2, %{status: "traps are happy"}) + {:ok, a3} = CommonAPI.post(user2, %{status: "Trees Are "}) + {:ok, a4} = CommonAPI.post(user2, %{status: "Agent Smith "}) + {:ok, a5} = CommonAPI.post(user1, %{status: "Red or Blue "}) + + {:ok, _} = CommonAPI.favorite(user, a4.id) + {:ok, _} = CommonAPI.favorite(other_user, a3.id) + {:ok, _} = CommonAPI.favorite(user, a3.id) + {:ok, _} = CommonAPI.favorite(other_user, a5.id) + {:ok, _} = CommonAPI.favorite(user, a5.id) + {:ok, _} = CommonAPI.favorite(other_user, a4.id) + {:ok, _} = CommonAPI.favorite(user, a1.id) + {:ok, _} = CommonAPI.favorite(other_user, a1.id) result = ActivityPub.fetch_favourites(user) assert Enum.map(result, & &1.id) == [a1.id, a5.id, a3.id, a4.id] @@ -1770,11 +1518,9 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do activity = %Activity{activity | object: nil} - assert [%Notification{activity: ^activity}] = - Notification.for_user(follower, %{with_move: true}) + assert [%Notification{activity: ^activity}] = Notification.for_user(follower) - assert [%Notification{activity: ^activity}] = - Notification.for_user(follower_move_opted_out, %{with_move: true}) + assert [%Notification{activity: ^activity}] = Notification.for_user(follower_move_opted_out) end test "old user must be in the new user's `also_known_as` list" do @@ -1785,4 +1531,543 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do ActivityPub.move(old_user, new_user) end end + + test "doesn't retrieve replies activities with exclude_replies" do + user = insert(:user) + + {:ok, activity} = CommonAPI.post(user, %{status: "yeah"}) + + {:ok, _reply} = CommonAPI.post(user, %{status: "yeah", in_reply_to_status_id: activity.id}) + + [result] = ActivityPub.fetch_public_activities(%{"exclude_replies" => "true"}) + + assert result.id == activity.id + + assert length(ActivityPub.fetch_public_activities()) == 2 + end + + describe "replies filtering with public messages" do + setup :public_messages + + test "public timeline", %{users: %{u1: user}} do + activities_ids = + %{} + |> Map.put("type", ["Create", "Announce"]) + |> Map.put("local_only", false) + |> Map.put("blocking_user", user) + |> Map.put("muting_user", user) + |> Map.put("reply_filtering_user", user) + |> ActivityPub.fetch_public_activities() + |> Enum.map(& &1.id) + + assert length(activities_ids) == 16 + end + + test "public timeline with reply_visibility `following`", %{ + users: %{u1: user}, + u1: u1, + u2: u2, + u3: u3, + u4: u4, + activities: activities + } do + activities_ids = + %{} + |> Map.put("type", ["Create", "Announce"]) + |> Map.put("local_only", false) + |> Map.put("blocking_user", user) + |> Map.put("muting_user", user) + |> Map.put("reply_visibility", "following") + |> Map.put("reply_filtering_user", user) + |> ActivityPub.fetch_public_activities() + |> Enum.map(& &1.id) + + assert length(activities_ids) == 14 + + visible_ids = + Map.values(u1) ++ Map.values(u2) ++ Map.values(u4) ++ Map.values(activities) ++ [u3[:r1]] + + assert Enum.all?(visible_ids, &(&1 in activities_ids)) + end + + test "public timeline with reply_visibility `self`", %{ + users: %{u1: user}, + u1: u1, + u2: u2, + u3: u3, + u4: u4, + activities: activities + } do + activities_ids = + %{} + |> Map.put("type", ["Create", "Announce"]) + |> Map.put("local_only", false) + |> Map.put("blocking_user", user) + |> Map.put("muting_user", user) + |> Map.put("reply_visibility", "self") + |> Map.put("reply_filtering_user", user) + |> ActivityPub.fetch_public_activities() + |> Enum.map(& &1.id) + + assert length(activities_ids) == 10 + visible_ids = Map.values(u1) ++ [u2[:r1], u3[:r1], u4[:r1]] ++ Map.values(activities) + assert Enum.all?(visible_ids, &(&1 in activities_ids)) + end + + test "home timeline", %{ + users: %{u1: user}, + activities: activities, + u1: u1, + u2: u2, + u3: u3, + u4: u4 + } do + params = + %{} + |> Map.put("type", ["Create", "Announce"]) + |> Map.put("blocking_user", user) + |> Map.put("muting_user", user) + |> Map.put("user", user) + |> Map.put("reply_filtering_user", user) + + activities_ids = + ActivityPub.fetch_activities([user.ap_id | User.following(user)], params) + |> Enum.map(& &1.id) + + assert length(activities_ids) == 13 + + visible_ids = + Map.values(u1) ++ + Map.values(u3) ++ + [ + activities[:a1], + activities[:a2], + activities[:a4], + u2[:r1], + u2[:r3], + u4[:r1], + u4[:r2] + ] + + assert Enum.all?(visible_ids, &(&1 in activities_ids)) + end + + test "home timeline with reply_visibility `following`", %{ + users: %{u1: user}, + activities: activities, + u1: u1, + u2: u2, + u3: u3, + u4: u4 + } do + params = + %{} + |> Map.put("type", ["Create", "Announce"]) + |> Map.put("blocking_user", user) + |> Map.put("muting_user", user) + |> Map.put("user", user) + |> Map.put("reply_visibility", "following") + |> Map.put("reply_filtering_user", user) + + activities_ids = + ActivityPub.fetch_activities([user.ap_id | User.following(user)], params) + |> Enum.map(& &1.id) + + assert length(activities_ids) == 11 + + visible_ids = + Map.values(u1) ++ + [ + activities[:a1], + activities[:a2], + activities[:a4], + u2[:r1], + u2[:r3], + u3[:r1], + u4[:r1], + u4[:r2] + ] + + assert Enum.all?(visible_ids, &(&1 in activities_ids)) + end + + test "home timeline with reply_visibility `self`", %{ + users: %{u1: user}, + activities: activities, + u1: u1, + u2: u2, + u3: u3, + u4: u4 + } do + params = + %{} + |> Map.put("type", ["Create", "Announce"]) + |> Map.put("blocking_user", user) + |> Map.put("muting_user", user) + |> Map.put("user", user) + |> Map.put("reply_visibility", "self") + |> Map.put("reply_filtering_user", user) + + activities_ids = + ActivityPub.fetch_activities([user.ap_id | User.following(user)], params) + |> Enum.map(& &1.id) + + assert length(activities_ids) == 9 + + visible_ids = + Map.values(u1) ++ + [ + activities[:a1], + activities[:a2], + activities[:a4], + u2[:r1], + u3[:r1], + u4[:r1] + ] + + assert Enum.all?(visible_ids, &(&1 in activities_ids)) + end + end + + describe "replies filtering with private messages" do + setup :private_messages + + test "public timeline", %{users: %{u1: user}} do + activities_ids = + %{} + |> Map.put("type", ["Create", "Announce"]) + |> Map.put("local_only", false) + |> Map.put("blocking_user", user) + |> Map.put("muting_user", user) + |> Map.put("user", user) + |> ActivityPub.fetch_public_activities() + |> Enum.map(& &1.id) + + assert activities_ids == [] + end + + test "public timeline with default reply_visibility `following`", %{users: %{u1: user}} do + activities_ids = + %{} + |> Map.put("type", ["Create", "Announce"]) + |> Map.put("local_only", false) + |> Map.put("blocking_user", user) + |> Map.put("muting_user", user) + |> Map.put("reply_visibility", "following") + |> Map.put("reply_filtering_user", user) + |> Map.put("user", user) + |> ActivityPub.fetch_public_activities() + |> Enum.map(& &1.id) + + assert activities_ids == [] + end + + test "public timeline with default reply_visibility `self`", %{users: %{u1: user}} do + activities_ids = + %{} + |> Map.put("type", ["Create", "Announce"]) + |> Map.put("local_only", false) + |> Map.put("blocking_user", user) + |> Map.put("muting_user", user) + |> Map.put("reply_visibility", "self") + |> Map.put("reply_filtering_user", user) + |> Map.put("user", user) + |> ActivityPub.fetch_public_activities() + |> Enum.map(& &1.id) + + assert activities_ids == [] + end + + test "home timeline", %{users: %{u1: user}} do + params = + %{} + |> Map.put("type", ["Create", "Announce"]) + |> Map.put("blocking_user", user) + |> Map.put("muting_user", user) + |> Map.put("user", user) + + activities_ids = + ActivityPub.fetch_activities([user.ap_id | User.following(user)], params) + |> Enum.map(& &1.id) + + assert length(activities_ids) == 12 + end + + test "home timeline with default reply_visibility `following`", %{users: %{u1: user}} do + params = + %{} + |> Map.put("type", ["Create", "Announce"]) + |> Map.put("blocking_user", user) + |> Map.put("muting_user", user) + |> Map.put("user", user) + |> Map.put("reply_visibility", "following") + |> Map.put("reply_filtering_user", user) + + activities_ids = + ActivityPub.fetch_activities([user.ap_id | User.following(user)], params) + |> Enum.map(& &1.id) + + assert length(activities_ids) == 12 + end + + test "home timeline with default reply_visibility `self`", %{ + users: %{u1: user}, + activities: activities, + u1: u1, + u2: u2, + u3: u3, + u4: u4 + } do + params = + %{} + |> Map.put("type", ["Create", "Announce"]) + |> Map.put("blocking_user", user) + |> Map.put("muting_user", user) + |> Map.put("user", user) + |> Map.put("reply_visibility", "self") + |> Map.put("reply_filtering_user", user) + + activities_ids = + ActivityPub.fetch_activities([user.ap_id | User.following(user)], params) + |> Enum.map(& &1.id) + + assert length(activities_ids) == 10 + + visible_ids = + Map.values(u1) ++ Map.values(u4) ++ [u2[:r1], u3[:r1]] ++ Map.values(activities) + + assert Enum.all?(visible_ids, &(&1 in activities_ids)) + end + end + + defp public_messages(_) do + [u1, u2, u3, u4] = insert_list(4, :user) + {:ok, u1} = User.follow(u1, u2) + {:ok, u2} = User.follow(u2, u1) + {:ok, u1} = User.follow(u1, u4) + {:ok, u4} = User.follow(u4, u1) + + {:ok, u2} = User.follow(u2, u3) + {:ok, u3} = User.follow(u3, u2) + + {:ok, a1} = CommonAPI.post(u1, %{status: "Status"}) + + {:ok, r1_1} = + CommonAPI.post(u2, %{ + status: "@#{u1.nickname} reply from u2 to u1", + in_reply_to_status_id: a1.id + }) + + {:ok, r1_2} = + CommonAPI.post(u3, %{ + status: "@#{u1.nickname} reply from u3 to u1", + in_reply_to_status_id: a1.id + }) + + {:ok, r1_3} = + CommonAPI.post(u4, %{ + status: "@#{u1.nickname} reply from u4 to u1", + in_reply_to_status_id: a1.id + }) + + {:ok, a2} = CommonAPI.post(u2, %{status: "Status"}) + + {:ok, r2_1} = + CommonAPI.post(u1, %{ + status: "@#{u2.nickname} reply from u1 to u2", + in_reply_to_status_id: a2.id + }) + + {:ok, r2_2} = + CommonAPI.post(u3, %{ + status: "@#{u2.nickname} reply from u3 to u2", + in_reply_to_status_id: a2.id + }) + + {:ok, r2_3} = + CommonAPI.post(u4, %{ + status: "@#{u2.nickname} reply from u4 to u2", + in_reply_to_status_id: a2.id + }) + + {:ok, a3} = CommonAPI.post(u3, %{status: "Status"}) + + {:ok, r3_1} = + CommonAPI.post(u1, %{ + status: "@#{u3.nickname} reply from u1 to u3", + in_reply_to_status_id: a3.id + }) + + {:ok, r3_2} = + CommonAPI.post(u2, %{ + status: "@#{u3.nickname} reply from u2 to u3", + in_reply_to_status_id: a3.id + }) + + {:ok, r3_3} = + CommonAPI.post(u4, %{ + status: "@#{u3.nickname} reply from u4 to u3", + in_reply_to_status_id: a3.id + }) + + {:ok, a4} = CommonAPI.post(u4, %{status: "Status"}) + + {:ok, r4_1} = + CommonAPI.post(u1, %{ + status: "@#{u4.nickname} reply from u1 to u4", + in_reply_to_status_id: a4.id + }) + + {:ok, r4_2} = + CommonAPI.post(u2, %{ + status: "@#{u4.nickname} reply from u2 to u4", + in_reply_to_status_id: a4.id + }) + + {:ok, r4_3} = + CommonAPI.post(u3, %{ + status: "@#{u4.nickname} reply from u3 to u4", + in_reply_to_status_id: a4.id + }) + + {:ok, + users: %{u1: u1, u2: u2, u3: u3, u4: u4}, + activities: %{a1: a1.id, a2: a2.id, a3: a3.id, a4: a4.id}, + u1: %{r1: r1_1.id, r2: r1_2.id, r3: r1_3.id}, + u2: %{r1: r2_1.id, r2: r2_2.id, r3: r2_3.id}, + u3: %{r1: r3_1.id, r2: r3_2.id, r3: r3_3.id}, + u4: %{r1: r4_1.id, r2: r4_2.id, r3: r4_3.id}} + end + + defp private_messages(_) do + [u1, u2, u3, u4] = insert_list(4, :user) + {:ok, u1} = User.follow(u1, u2) + {:ok, u2} = User.follow(u2, u1) + {:ok, u1} = User.follow(u1, u3) + {:ok, u3} = User.follow(u3, u1) + {:ok, u1} = User.follow(u1, u4) + {:ok, u4} = User.follow(u4, u1) + + {:ok, u2} = User.follow(u2, u3) + {:ok, u3} = User.follow(u3, u2) + + {:ok, a1} = CommonAPI.post(u1, %{status: "Status", visibility: "private"}) + + {:ok, r1_1} = + CommonAPI.post(u2, %{ + status: "@#{u1.nickname} reply from u2 to u1", + in_reply_to_status_id: a1.id, + visibility: "private" + }) + + {:ok, r1_2} = + CommonAPI.post(u3, %{ + status: "@#{u1.nickname} reply from u3 to u1", + in_reply_to_status_id: a1.id, + visibility: "private" + }) + + {:ok, r1_3} = + CommonAPI.post(u4, %{ + status: "@#{u1.nickname} reply from u4 to u1", + in_reply_to_status_id: a1.id, + visibility: "private" + }) + + {:ok, a2} = CommonAPI.post(u2, %{status: "Status", visibility: "private"}) + + {:ok, r2_1} = + CommonAPI.post(u1, %{ + status: "@#{u2.nickname} reply from u1 to u2", + in_reply_to_status_id: a2.id, + visibility: "private" + }) + + {:ok, r2_2} = + CommonAPI.post(u3, %{ + status: "@#{u2.nickname} reply from u3 to u2", + in_reply_to_status_id: a2.id, + visibility: "private" + }) + + {:ok, a3} = CommonAPI.post(u3, %{status: "Status", visibility: "private"}) + + {:ok, r3_1} = + CommonAPI.post(u1, %{ + status: "@#{u3.nickname} reply from u1 to u3", + in_reply_to_status_id: a3.id, + visibility: "private" + }) + + {:ok, r3_2} = + CommonAPI.post(u2, %{ + status: "@#{u3.nickname} reply from u2 to u3", + in_reply_to_status_id: a3.id, + visibility: "private" + }) + + {:ok, a4} = CommonAPI.post(u4, %{status: "Status", visibility: "private"}) + + {:ok, r4_1} = + CommonAPI.post(u1, %{ + status: "@#{u4.nickname} reply from u1 to u4", + in_reply_to_status_id: a4.id, + visibility: "private" + }) + + {:ok, + users: %{u1: u1, u2: u2, u3: u3, u4: u4}, + activities: %{a1: a1.id, a2: a2.id, a3: a3.id, a4: a4.id}, + u1: %{r1: r1_1.id, r2: r1_2.id, r3: r1_3.id}, + u2: %{r1: r2_1.id, r2: r2_2.id}, + u3: %{r1: r3_1.id, r2: r3_2.id}, + u4: %{r1: r4_1.id}} + end + + describe "maybe_update_follow_information/1" do + setup do + clear_config([:instance, :external_user_synchronization], true) + + user = %{ + local: false, + ap_id: "https://gensokyo.2hu/users/raymoo", + following_address: "https://gensokyo.2hu/users/following", + follower_address: "https://gensokyo.2hu/users/followers", + type: "Person" + } + + %{user: user} + end + + test "logs an error when it can't fetch the info", %{user: user} do + assert capture_log(fn -> + ActivityPub.maybe_update_follow_information(user) + end) =~ "Follower/Following counter update for #{user.ap_id} failed" + end + + test "just returns the input if the user type is Application", %{ + user: user + } do + user = + user + |> Map.put(:type, "Application") + + refute capture_log(fn -> + assert ^user = ActivityPub.maybe_update_follow_information(user) + end) =~ "Follower/Following counter update for #{user.ap_id} failed" + end + + test "it just returns the input if the user has no following/follower addresses", %{ + user: user + } do + user = + user + |> Map.put(:following_address, nil) + |> Map.put(:follower_address, nil) + + refute capture_log(fn -> + assert ^user = ActivityPub.maybe_update_follow_information(user) + end) =~ "Follower/Following counter update for #{user.ap_id} failed" + end + end end diff --git a/test/web/activity_pub/mrf/anti_followbot_policy_test.exs b/test/web/activity_pub/mrf/anti_followbot_policy_test.exs index 37a7bfcf7..fca0de7c6 100644 --- a/test/web/activity_pub/mrf/anti_followbot_policy_test.exs +++ b/test/web/activity_pub/mrf/anti_followbot_policy_test.exs @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.ActivityPub.MRF.AntiFollowbotPolicyTest do diff --git a/test/web/activity_pub/mrf/anti_link_spam_policy_test.exs b/test/web/activity_pub/mrf/anti_link_spam_policy_test.exs index b524fdd23..1a13699be 100644 --- a/test/web/activity_pub/mrf/anti_link_spam_policy_test.exs +++ b/test/web/activity_pub/mrf/anti_link_spam_policy_test.exs @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.ActivityPub.MRF.AntiLinkSpamPolicyTest do @@ -110,6 +110,15 @@ defmodule Pleroma.Web.ActivityPub.MRF.AntiLinkSpamPolicyTest do end describe "with unknown actors" do + setup do + Tesla.Mock.mock(fn + %{method: :get, url: "http://invalid.actor"} -> + %Tesla.Env{status: 500, body: ""} + end) + + :ok + end + test "it rejects posts without links" do message = @linkless_message diff --git a/test/web/activity_pub/mrf/ensure_re_prepended_test.exs b/test/web/activity_pub/mrf/ensure_re_prepended_test.exs index dbc8b9e80..38ddec5bb 100644 --- a/test/web/activity_pub/mrf/ensure_re_prepended_test.exs +++ b/test/web/activity_pub/mrf/ensure_re_prepended_test.exs @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.ActivityPub.MRF.EnsureRePrependedTest do diff --git a/test/web/activity_pub/mrf/hellthread_policy_test.exs b/test/web/activity_pub/mrf/hellthread_policy_test.exs index a78752a12..95ef0b168 100644 --- a/test/web/activity_pub/mrf/hellthread_policy_test.exs +++ b/test/web/activity_pub/mrf/hellthread_policy_test.exs @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.ActivityPub.MRF.HellthreadPolicyTest do @@ -26,7 +26,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.HellthreadPolicyTest do [user: user, message: message] end - clear_config(:mrf_hellthread) + setup do: clear_config(:mrf_hellthread) describe "reject" do test "rejects the message if the recipient count is above reject_threshold", %{ diff --git a/test/web/activity_pub/mrf/keyword_policy_test.exs b/test/web/activity_pub/mrf/keyword_policy_test.exs index d950ddd56..fd1f7aec8 100644 --- a/test/web/activity_pub/mrf/keyword_policy_test.exs +++ b/test/web/activity_pub/mrf/keyword_policy_test.exs @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.ActivityPub.MRF.KeywordPolicyTest do @@ -7,7 +7,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.KeywordPolicyTest do alias Pleroma.Web.ActivityPub.MRF.KeywordPolicy - clear_config(:mrf_keyword) + setup do: clear_config(:mrf_keyword) setup do Pleroma.Config.put([:mrf_keyword], %{reject: [], federated_timeline_removal: [], replace: []}) diff --git a/test/web/activity_pub/mrf/mediaproxy_warming_policy_test.exs b/test/web/activity_pub/mrf/mediaproxy_warming_policy_test.exs index 95a809d25..313d59a66 100644 --- a/test/web/activity_pub/mrf/mediaproxy_warming_policy_test.exs +++ b/test/web/activity_pub/mrf/mediaproxy_warming_policy_test.exs @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicyTest do diff --git a/test/web/activity_pub/mrf/mention_policy_test.exs b/test/web/activity_pub/mrf/mention_policy_test.exs index 93a55850f..aa003bef5 100644 --- a/test/web/activity_pub/mrf/mention_policy_test.exs +++ b/test/web/activity_pub/mrf/mention_policy_test.exs @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.ActivityPub.MRF.MentionPolicyTest do @@ -7,7 +7,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.MentionPolicyTest do alias Pleroma.Web.ActivityPub.MRF.MentionPolicy - clear_config(:mrf_mention) + setup do: clear_config(:mrf_mention) test "pass filter if allow list is empty" do Pleroma.Config.delete([:mrf_mention]) diff --git a/test/web/activity_pub/mrf/mrf_test.exs b/test/web/activity_pub/mrf/mrf_test.exs index 04709df17..c941066f2 100644 --- a/test/web/activity_pub/mrf/mrf_test.exs +++ b/test/web/activity_pub/mrf/mrf_test.exs @@ -60,7 +60,7 @@ defmodule Pleroma.Web.ActivityPub.MRFTest do end describe "describe/0" do - clear_config([:instance, :rewrite_policy]) + setup do: clear_config([:instance, :rewrite_policy]) test "it works as expected with noop policy" do expected = %{ diff --git a/test/web/activity_pub/mrf/no_placeholder_text_policy_test.exs b/test/web/activity_pub/mrf/no_placeholder_text_policy_test.exs index 63ed71129..64ea61dd4 100644 --- a/test/web/activity_pub/mrf/no_placeholder_text_policy_test.exs +++ b/test/web/activity_pub/mrf/no_placeholder_text_policy_test.exs @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.ActivityPub.MRF.NoPlaceholderTextPolicyTest do diff --git a/test/web/activity_pub/mrf/normalize_markup_test.exs b/test/web/activity_pub/mrf/normalize_markup_test.exs index 0207be56b..9b39c45bd 100644 --- a/test/web/activity_pub/mrf/normalize_markup_test.exs +++ b/test/web/activity_pub/mrf/normalize_markup_test.exs @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.ActivityPub.MRF.NormalizeMarkupTest do diff --git a/test/web/activity_pub/mrf/object_age_policy_test.exs b/test/web/activity_pub/mrf/object_age_policy_test.exs index 643609da4..b0fb753bd 100644 --- a/test/web/activity_pub/mrf/object_age_policy_test.exs +++ b/test/web/activity_pub/mrf/object_age_policy_test.exs @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.ActivityPub.MRF.ObjectAgePolicyTest do @@ -9,38 +9,49 @@ defmodule Pleroma.Web.ActivityPub.MRF.ObjectAgePolicyTest do alias Pleroma.Web.ActivityPub.MRF.ObjectAgePolicy alias Pleroma.Web.ActivityPub.Visibility - clear_config([:mrf_object_age]) do - Config.put(:mrf_object_age, - threshold: 172_800, - actions: [:delist, :strip_followers] - ) - end + setup do: + clear_config(:mrf_object_age, + threshold: 172_800, + actions: [:delist, :strip_followers] + ) setup_all do Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end) :ok end + defp get_old_message do + File.read!("test/fixtures/mastodon-post-activity.json") + |> Poison.decode!() + end + + defp get_new_message do + old_message = get_old_message() + + new_object = + old_message + |> Map.get("object") + |> Map.put("published", DateTime.utc_now() |> DateTime.to_iso8601()) + + old_message + |> Map.put("object", new_object) + end + describe "with reject action" do test "it rejects an old post" do Config.put([:mrf_object_age, :actions], [:reject]) - data = - File.read!("test/fixtures/mastodon-post-activity.json") - |> Poison.decode!() + data = get_old_message() - {:reject, _} = ObjectAgePolicy.filter(data) + assert match?({:reject, _}, ObjectAgePolicy.filter(data)) end test "it allows a new post" do Config.put([:mrf_object_age, :actions], [:reject]) - data = - File.read!("test/fixtures/mastodon-post-activity.json") - |> Poison.decode!() - |> Map.put("published", DateTime.utc_now() |> DateTime.to_iso8601()) + data = get_new_message() - {:ok, _} = ObjectAgePolicy.filter(data) + assert match?({:ok, _}, ObjectAgePolicy.filter(data)) end end @@ -48,9 +59,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.ObjectAgePolicyTest do test "it delists an old post" do Config.put([:mrf_object_age, :actions], [:delist]) - data = - File.read!("test/fixtures/mastodon-post-activity.json") - |> Poison.decode!() + data = get_old_message() {:ok, _u} = User.get_or_fetch_by_ap_id(data["actor"]) @@ -62,14 +71,11 @@ defmodule Pleroma.Web.ActivityPub.MRF.ObjectAgePolicyTest do test "it allows a new post" do Config.put([:mrf_object_age, :actions], [:delist]) - data = - File.read!("test/fixtures/mastodon-post-activity.json") - |> Poison.decode!() - |> Map.put("published", DateTime.utc_now() |> DateTime.to_iso8601()) + data = get_new_message() {:ok, _user} = User.get_or_fetch_by_ap_id(data["actor"]) - {:ok, ^data} = ObjectAgePolicy.filter(data) + assert match?({:ok, ^data}, ObjectAgePolicy.filter(data)) end end @@ -77,9 +83,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.ObjectAgePolicyTest do test "it strips followers collections from an old post" do Config.put([:mrf_object_age, :actions], [:strip_followers]) - data = - File.read!("test/fixtures/mastodon-post-activity.json") - |> Poison.decode!() + data = get_old_message() {:ok, user} = User.get_or_fetch_by_ap_id(data["actor"]) @@ -92,14 +96,11 @@ defmodule Pleroma.Web.ActivityPub.MRF.ObjectAgePolicyTest do test "it allows a new post" do Config.put([:mrf_object_age, :actions], [:strip_followers]) - data = - File.read!("test/fixtures/mastodon-post-activity.json") - |> Poison.decode!() - |> Map.put("published", DateTime.utc_now() |> DateTime.to_iso8601()) + data = get_new_message() {:ok, _u} = User.get_or_fetch_by_ap_id(data["actor"]) - {:ok, ^data} = ObjectAgePolicy.filter(data) + assert match?({:ok, ^data}, ObjectAgePolicy.filter(data)) end end end diff --git a/test/web/activity_pub/mrf/reject_non_public_test.exs b/test/web/activity_pub/mrf/reject_non_public_test.exs index fc1d190bb..f36299b86 100644 --- a/test/web/activity_pub/mrf/reject_non_public_test.exs +++ b/test/web/activity_pub/mrf/reject_non_public_test.exs @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.ActivityPub.MRF.RejectNonPublicTest do @@ -8,7 +8,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.RejectNonPublicTest do alias Pleroma.Web.ActivityPub.MRF.RejectNonPublic - clear_config([:mrf_rejectnonpublic]) + setup do: clear_config([:mrf_rejectnonpublic]) describe "public message" do test "it's allowed when address is public" do diff --git a/test/web/activity_pub/mrf/simple_policy_test.exs b/test/web/activity_pub/mrf/simple_policy_test.exs index df0f223f8..b7b9bc6a2 100644 --- a/test/web/activity_pub/mrf/simple_policy_test.exs +++ b/test/web/activity_pub/mrf/simple_policy_test.exs @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicyTest do @@ -8,18 +8,18 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicyTest do alias Pleroma.Config alias Pleroma.Web.ActivityPub.MRF.SimplePolicy - clear_config([:mrf_simple]) do - Config.put(:mrf_simple, - media_removal: [], - media_nsfw: [], - federated_timeline_removal: [], - report_removal: [], - reject: [], - accept: [], - avatar_removal: [], - banner_removal: [] - ) - end + setup do: + clear_config(:mrf_simple, + media_removal: [], + media_nsfw: [], + federated_timeline_removal: [], + report_removal: [], + reject: [], + accept: [], + avatar_removal: [], + banner_removal: [], + reject_deletes: [] + ) describe "when :media_removal" do test "is empty" do @@ -383,6 +383,66 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicyTest do end end + describe "when :reject_deletes is empty" do + setup do: Config.put([:mrf_simple, :reject_deletes], []) + + test "it accepts deletions even from rejected servers" do + Config.put([:mrf_simple, :reject], ["remote.instance"]) + + deletion_message = build_remote_deletion_message() + + assert SimplePolicy.filter(deletion_message) == {:ok, deletion_message} + end + + test "it accepts deletions even from non-whitelisted servers" do + Config.put([:mrf_simple, :accept], ["non.matching.remote"]) + + deletion_message = build_remote_deletion_message() + + assert SimplePolicy.filter(deletion_message) == {:ok, deletion_message} + end + end + + describe "when :reject_deletes is not empty but it doesn't have a matching host" do + setup do: Config.put([:mrf_simple, :reject_deletes], ["non.matching.remote"]) + + test "it accepts deletions even from rejected servers" do + Config.put([:mrf_simple, :reject], ["remote.instance"]) + + deletion_message = build_remote_deletion_message() + + assert SimplePolicy.filter(deletion_message) == {:ok, deletion_message} + end + + test "it accepts deletions even from non-whitelisted servers" do + Config.put([:mrf_simple, :accept], ["non.matching.remote"]) + + deletion_message = build_remote_deletion_message() + + assert SimplePolicy.filter(deletion_message) == {:ok, deletion_message} + end + end + + describe "when :reject_deletes has a matching host" do + setup do: Config.put([:mrf_simple, :reject_deletes], ["remote.instance"]) + + test "it rejects the deletion" do + deletion_message = build_remote_deletion_message() + + assert SimplePolicy.filter(deletion_message) == {:reject, nil} + end + end + + describe "when :reject_deletes match with wildcard domain" do + setup do: Config.put([:mrf_simple, :reject_deletes], ["*.remote.instance"]) + + test "it rejects the deletion" do + deletion_message = build_remote_deletion_message() + + assert SimplePolicy.filter(deletion_message) == {:reject, nil} + end + end + defp build_local_message do %{ "actor" => "#{Pleroma.Web.base_url()}/users/alice", @@ -409,4 +469,11 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicyTest do "type" => "Person" } end + + defp build_remote_deletion_message do + %{ + "type" => "Delete", + "actor" => "https://remote.instance/users/bob" + } + end end diff --git a/test/web/activity_pub/mrf/subchain_policy_test.exs b/test/web/activity_pub/mrf/subchain_policy_test.exs index 29065f612..fff66cb7e 100644 --- a/test/web/activity_pub/mrf/subchain_policy_test.exs +++ b/test/web/activity_pub/mrf/subchain_policy_test.exs @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.ActivityPub.MRF.SubchainPolicyTest do @@ -13,8 +13,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.SubchainPolicyTest do "type" => "Create", "object" => %{"content" => "hi"} } - - clear_config([:mrf_subchain, :match_actor]) + setup do: clear_config([:mrf_subchain, :match_actor]) test "it matches and processes subchains when the actor matches a configured target" do Pleroma.Config.put([:mrf_subchain, :match_actor], %{ diff --git a/test/web/activity_pub/mrf/tag_policy_test.exs b/test/web/activity_pub/mrf/tag_policy_test.exs index 4aa35311e..e7793641a 100644 --- a/test/web/activity_pub/mrf/tag_policy_test.exs +++ b/test/web/activity_pub/mrf/tag_policy_test.exs @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.ActivityPub.MRF.TagPolicyTest do diff --git a/test/web/activity_pub/mrf/user_allowlist_policy_test.exs b/test/web/activity_pub/mrf/user_allowlist_policy_test.exs index 72084c0fd..724bae058 100644 --- a/test/web/activity_pub/mrf/user_allowlist_policy_test.exs +++ b/test/web/activity_pub/mrf/user_allowlist_policy_test.exs @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.ActivityPub.MRF.UserAllowListPolicyTest do use Pleroma.DataCase @@ -7,7 +7,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.UserAllowListPolicyTest do alias Pleroma.Web.ActivityPub.MRF.UserAllowListPolicy - clear_config([:mrf_user_allowlist, :localhost]) + setup do: clear_config([:mrf_user_allowlist, :localhost]) test "pass filter if allow list is empty" do actor = insert(:user) diff --git a/test/web/activity_pub/mrf/vocabulary_policy_test.exs b/test/web/activity_pub/mrf/vocabulary_policy_test.exs index 38309f9f1..69f22bb77 100644 --- a/test/web/activity_pub/mrf/vocabulary_policy_test.exs +++ b/test/web/activity_pub/mrf/vocabulary_policy_test.exs @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.ActivityPub.MRF.VocabularyPolicyTest do @@ -8,7 +8,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.VocabularyPolicyTest do alias Pleroma.Web.ActivityPub.MRF.VocabularyPolicy describe "accept" do - clear_config([:mrf_vocabulary, :accept]) + setup do: clear_config([:mrf_vocabulary, :accept]) test "it accepts based on parent activity type" do Pleroma.Config.put([:mrf_vocabulary, :accept], ["Like"]) @@ -65,7 +65,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.VocabularyPolicyTest do end describe "reject" do - clear_config([:mrf_vocabulary, :reject]) + setup do: clear_config([:mrf_vocabulary, :reject]) test "it rejects based on parent activity type" do Pleroma.Config.put([:mrf_vocabulary, :reject], ["Like"]) diff --git a/test/web/activity_pub/object_validator_test.exs b/test/web/activity_pub/object_validator_test.exs new file mode 100644 index 000000000..96eff1c30 --- /dev/null +++ b/test/web/activity_pub/object_validator_test.exs @@ -0,0 +1,283 @@ +defmodule Pleroma.Web.ActivityPub.ObjectValidatorTest do + use Pleroma.DataCase + + alias Pleroma.Object + alias Pleroma.Web.ActivityPub.Builder + alias Pleroma.Web.ActivityPub.ObjectValidator + alias Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator + alias Pleroma.Web.ActivityPub.Utils + alias Pleroma.Web.CommonAPI + + import Pleroma.Factory + + describe "EmojiReacts" do + setup do + user = insert(:user) + {:ok, post_activity} = CommonAPI.post(user, %{status: "uguu"}) + + object = Pleroma.Object.get_by_ap_id(post_activity.data["object"]) + + {:ok, valid_emoji_react, []} = Builder.emoji_react(user, object, "👌") + + %{user: user, post_activity: post_activity, valid_emoji_react: valid_emoji_react} + end + + test "it validates a valid EmojiReact", %{valid_emoji_react: valid_emoji_react} do + assert {:ok, _, _} = ObjectValidator.validate(valid_emoji_react, []) + end + + test "it is not valid without a 'content' field", %{valid_emoji_react: valid_emoji_react} do + without_content = + valid_emoji_react + |> Map.delete("content") + + {:error, cng} = ObjectValidator.validate(without_content, []) + + refute cng.valid? + assert {:content, {"can't be blank", [validation: :required]}} in cng.errors + end + + test "it is not valid with a non-emoji content field", %{valid_emoji_react: valid_emoji_react} do + without_emoji_content = + valid_emoji_react + |> Map.put("content", "x") + + {:error, cng} = ObjectValidator.validate(without_emoji_content, []) + + refute cng.valid? + + assert {:content, {"must be a single character emoji", []}} in cng.errors + end + end + + describe "Undos" do + setup do + user = insert(:user) + {:ok, post_activity} = CommonAPI.post(user, %{status: "uguu"}) + {:ok, like} = CommonAPI.favorite(user, post_activity.id) + {:ok, valid_like_undo, []} = Builder.undo(user, like) + + %{user: user, like: like, valid_like_undo: valid_like_undo} + end + + test "it validates a basic like undo", %{valid_like_undo: valid_like_undo} do + assert {:ok, _, _} = ObjectValidator.validate(valid_like_undo, []) + end + + test "it does not validate if the actor of the undo is not the actor of the object", %{ + valid_like_undo: valid_like_undo + } do + other_user = insert(:user, ap_id: "https://gensokyo.2hu/users/raymoo") + + bad_actor = + valid_like_undo + |> Map.put("actor", other_user.ap_id) + + {:error, cng} = ObjectValidator.validate(bad_actor, []) + + assert {:actor, {"not the same as object actor", []}} in cng.errors + end + + test "it does not validate if the object is missing", %{valid_like_undo: valid_like_undo} do + missing_object = + valid_like_undo + |> Map.put("object", "https://gensokyo.2hu/objects/1") + + {:error, cng} = ObjectValidator.validate(missing_object, []) + + assert {:object, {"can't find object", []}} in cng.errors + assert length(cng.errors) == 1 + end + end + + describe "deletes" do + setup do + user = insert(:user) + {:ok, post_activity} = CommonAPI.post(user, %{status: "cancel me daddy"}) + + {:ok, valid_post_delete, _} = Builder.delete(user, post_activity.data["object"]) + {:ok, valid_user_delete, _} = Builder.delete(user, user.ap_id) + + %{user: user, valid_post_delete: valid_post_delete, valid_user_delete: valid_user_delete} + end + + test "it is valid for a post deletion", %{valid_post_delete: valid_post_delete} do + {:ok, valid_post_delete, _} = ObjectValidator.validate(valid_post_delete, []) + + assert valid_post_delete["deleted_activity_id"] + end + + test "it is invalid if the object isn't in a list of certain types", %{ + valid_post_delete: valid_post_delete + } do + object = Object.get_by_ap_id(valid_post_delete["object"]) + + data = + object.data + |> Map.put("type", "Like") + + {:ok, _object} = + object + |> Ecto.Changeset.change(%{data: data}) + |> Object.update_and_set_cache() + + {:error, cng} = ObjectValidator.validate(valid_post_delete, []) + assert {:object, {"object not in allowed types", []}} in cng.errors + end + + test "it is valid for a user deletion", %{valid_user_delete: valid_user_delete} do + assert match?({:ok, _, _}, ObjectValidator.validate(valid_user_delete, [])) + end + + test "it's invalid if the id is missing", %{valid_post_delete: valid_post_delete} do + no_id = + valid_post_delete + |> Map.delete("id") + + {:error, cng} = ObjectValidator.validate(no_id, []) + + assert {:id, {"can't be blank", [validation: :required]}} in cng.errors + end + + test "it's invalid if the object doesn't exist", %{valid_post_delete: valid_post_delete} do + missing_object = + valid_post_delete + |> Map.put("object", "http://does.not/exist") + + {:error, cng} = ObjectValidator.validate(missing_object, []) + + assert {:object, {"can't find object", []}} in cng.errors + end + + test "it's invalid if the actor of the object and the actor of delete are from different domains", + %{valid_post_delete: valid_post_delete} do + valid_user = insert(:user) + + valid_other_actor = + valid_post_delete + |> Map.put("actor", valid_user.ap_id) + + assert match?({:ok, _, _}, ObjectValidator.validate(valid_other_actor, [])) + + invalid_other_actor = + valid_post_delete + |> Map.put("actor", "https://gensokyo.2hu/users/raymoo") + + {:error, cng} = ObjectValidator.validate(invalid_other_actor, []) + + assert {:actor, {"is not allowed to delete object", []}} in cng.errors + end + + test "it's valid if the actor of the object is a local superuser", + %{valid_post_delete: valid_post_delete} do + user = + insert(:user, local: true, is_moderator: true, ap_id: "https://gensokyo.2hu/users/raymoo") + + valid_other_actor = + valid_post_delete + |> Map.put("actor", user.ap_id) + + {:ok, _, meta} = ObjectValidator.validate(valid_other_actor, []) + assert meta[:do_not_federate] + end + end + + describe "likes" do + setup do + user = insert(:user) + {:ok, post_activity} = CommonAPI.post(user, %{status: "uguu"}) + + valid_like = %{ + "to" => [user.ap_id], + "cc" => [], + "type" => "Like", + "id" => Utils.generate_activity_id(), + "object" => post_activity.data["object"], + "actor" => user.ap_id, + "context" => "a context" + } + + %{valid_like: valid_like, user: user, post_activity: post_activity} + end + + test "returns ok when called in the ObjectValidator", %{valid_like: valid_like} do + {:ok, object, _meta} = ObjectValidator.validate(valid_like, []) + + assert "id" in Map.keys(object) + end + + test "is valid for a valid object", %{valid_like: valid_like} do + assert LikeValidator.cast_and_validate(valid_like).valid? + end + + test "sets the 'to' field to the object actor if no recipients are given", %{ + valid_like: valid_like, + user: user + } do + without_recipients = + valid_like + |> Map.delete("to") + + {:ok, object, _meta} = ObjectValidator.validate(without_recipients, []) + + assert object["to"] == [user.ap_id] + end + + test "sets the context field to the context of the object if no context is given", %{ + valid_like: valid_like, + post_activity: post_activity + } do + without_context = + valid_like + |> Map.delete("context") + + {:ok, object, _meta} = ObjectValidator.validate(without_context, []) + + assert object["context"] == post_activity.data["context"] + end + + test "it errors when the actor is missing or not known", %{valid_like: valid_like} do + without_actor = Map.delete(valid_like, "actor") + + refute LikeValidator.cast_and_validate(without_actor).valid? + + with_invalid_actor = Map.put(valid_like, "actor", "invalidactor") + + refute LikeValidator.cast_and_validate(with_invalid_actor).valid? + end + + test "it errors when the object is missing or not known", %{valid_like: valid_like} do + without_object = Map.delete(valid_like, "object") + + refute LikeValidator.cast_and_validate(without_object).valid? + + with_invalid_object = Map.put(valid_like, "object", "invalidobject") + + refute LikeValidator.cast_and_validate(with_invalid_object).valid? + end + + test "it errors when the actor has already like the object", %{ + valid_like: valid_like, + user: user, + post_activity: post_activity + } do + _like = CommonAPI.favorite(user, post_activity.id) + + refute LikeValidator.cast_and_validate(valid_like).valid? + end + + test "it works when actor or object are wrapped in maps", %{valid_like: valid_like} do + wrapped_like = + valid_like + |> Map.put("actor", %{"id" => valid_like["actor"]}) + |> Map.put("object", %{"id" => valid_like["object"]}) + + validated = LikeValidator.cast_and_validate(wrapped_like) + + assert validated.valid? + + assert {:actor, valid_like["actor"]} in validated.changes + assert {:object, valid_like["object"]} in validated.changes + end + end +end diff --git a/test/web/activity_pub/object_validators/note_validator_test.exs b/test/web/activity_pub/object_validators/note_validator_test.exs new file mode 100644 index 000000000..30c481ffb --- /dev/null +++ b/test/web/activity_pub/object_validators/note_validator_test.exs @@ -0,0 +1,35 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ActivityPub.ObjectValidators.NoteValidatorTest do + use Pleroma.DataCase + + alias Pleroma.Web.ActivityPub.ObjectValidators.NoteValidator + alias Pleroma.Web.ActivityPub.Utils + + import Pleroma.Factory + + describe "Notes" do + setup do + user = insert(:user) + + note = %{ + "id" => Utils.generate_activity_id(), + "type" => "Note", + "actor" => user.ap_id, + "to" => [user.follower_address], + "cc" => [], + "content" => "Hellow this is content.", + "context" => "xxx", + "summary" => "a post" + } + + %{user: user, note: note} + end + + test "a basic note validates", %{note: note} do + %{valid?: true} = NoteValidator.cast_and_validate(note) + end + end +end diff --git a/test/web/activity_pub/object_validators/types/date_time_test.exs b/test/web/activity_pub/object_validators/types/date_time_test.exs new file mode 100644 index 000000000..3e17a9497 --- /dev/null +++ b/test/web/activity_pub/object_validators/types/date_time_test.exs @@ -0,0 +1,32 @@ +defmodule Pleroma.Web.ActivityPub.ObjectValidators.Types.DateTimeTest do + alias Pleroma.Web.ActivityPub.ObjectValidators.Types.DateTime + use Pleroma.DataCase + + test "it validates an xsd:Datetime" do + valid_strings = [ + "2004-04-12T13:20:00", + "2004-04-12T13:20:15.5", + "2004-04-12T13:20:00-05:00", + "2004-04-12T13:20:00Z" + ] + + invalid_strings = [ + "2004-04-12T13:00", + "2004-04-1213:20:00", + "99-04-12T13:00", + "2004-04-12" + ] + + assert {:ok, "2004-04-01T12:00:00Z"} == DateTime.cast("2004-04-01T12:00:00Z") + + Enum.each(valid_strings, fn date_time -> + result = DateTime.cast(date_time) + assert {:ok, _} = result + end) + + Enum.each(invalid_strings, fn date_time -> + result = DateTime.cast(date_time) + assert :error == result + end) + end +end diff --git a/test/web/activity_pub/object_validators/types/object_id_test.exs b/test/web/activity_pub/object_validators/types/object_id_test.exs new file mode 100644 index 000000000..834213182 --- /dev/null +++ b/test/web/activity_pub/object_validators/types/object_id_test.exs @@ -0,0 +1,37 @@ +defmodule Pleroma.Web.ObjectValidators.Types.ObjectIDTest do + alias Pleroma.Web.ActivityPub.ObjectValidators.Types.ObjectID + use Pleroma.DataCase + + @uris [ + "http://lain.com/users/lain", + "http://lain.com", + "https://lain.com/object/1" + ] + + @non_uris [ + "https://", + "rin", + 1, + :x, + %{"1" => 2} + ] + + test "it accepts http uris" do + Enum.each(@uris, fn uri -> + assert {:ok, uri} == ObjectID.cast(uri) + end) + end + + test "it accepts an object with a nested uri id" do + Enum.each(@uris, fn uri -> + assert {:ok, uri} == ObjectID.cast(%{"id" => uri}) + end) + end + + test "it rejects non-uri strings" do + Enum.each(@non_uris, fn non_uri -> + assert :error == ObjectID.cast(non_uri) + assert :error == ObjectID.cast(%{"id" => non_uri}) + end) + end +end diff --git a/test/web/activity_pub/object_validators/types/recipients_test.exs b/test/web/activity_pub/object_validators/types/recipients_test.exs new file mode 100644 index 000000000..f278f039b --- /dev/null +++ b/test/web/activity_pub/object_validators/types/recipients_test.exs @@ -0,0 +1,27 @@ +defmodule Pleroma.Web.ObjectValidators.Types.RecipientsTest do + alias Pleroma.Web.ActivityPub.ObjectValidators.Types.Recipients + use Pleroma.DataCase + + test "it asserts that all elements of the list are object ids" do + list = ["https://lain.com/users/lain", "invalid"] + + assert :error == Recipients.cast(list) + end + + test "it works with a list" do + list = ["https://lain.com/users/lain"] + assert {:ok, list} == Recipients.cast(list) + end + + test "it works with a list with whole objects" do + list = ["https://lain.com/users/lain", %{"id" => "https://gensokyo.2hu/users/raymoo"}] + resulting_list = ["https://gensokyo.2hu/users/raymoo", "https://lain.com/users/lain"] + assert {:ok, resulting_list} == Recipients.cast(list) + end + + test "it turns a single string into a list" do + recipient = "https://lain.com/users/lain" + + assert {:ok, [recipient]} == Recipients.cast(recipient) + end +end diff --git a/test/web/activity_pub/pipeline_test.exs b/test/web/activity_pub/pipeline_test.exs new file mode 100644 index 000000000..f3c437498 --- /dev/null +++ b/test/web/activity_pub/pipeline_test.exs @@ -0,0 +1,87 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ActivityPub.PipelineTest do + use Pleroma.DataCase + + import Mock + import Pleroma.Factory + + describe "common_pipeline/2" do + test "it goes through validation, filtering, persisting, side effects and federation for local activities" do + activity = insert(:note_activity) + meta = [local: true] + + with_mocks([ + {Pleroma.Web.ActivityPub.ObjectValidator, [], [validate: fn o, m -> {:ok, o, m} end]}, + { + Pleroma.Web.ActivityPub.MRF, + [], + [filter: fn o -> {:ok, o} end] + }, + { + Pleroma.Web.ActivityPub.ActivityPub, + [], + [persist: fn o, m -> {:ok, o, m} end] + }, + { + Pleroma.Web.ActivityPub.SideEffects, + [], + [handle: fn o, m -> {:ok, o, m} end] + }, + { + Pleroma.Web.Federator, + [], + [publish: fn _o -> :ok end] + } + ]) do + assert {:ok, ^activity, ^meta} = + Pleroma.Web.ActivityPub.Pipeline.common_pipeline(activity, meta) + + assert_called(Pleroma.Web.ActivityPub.ObjectValidator.validate(activity, meta)) + assert_called(Pleroma.Web.ActivityPub.MRF.filter(activity)) + assert_called(Pleroma.Web.ActivityPub.ActivityPub.persist(activity, meta)) + assert_called(Pleroma.Web.ActivityPub.SideEffects.handle(activity, meta)) + assert_called(Pleroma.Web.Federator.publish(activity)) + end + end + + test "it goes through validation, filtering, persisting, side effects without federation for remote activities" do + activity = insert(:note_activity) + meta = [local: false] + + with_mocks([ + {Pleroma.Web.ActivityPub.ObjectValidator, [], [validate: fn o, m -> {:ok, o, m} end]}, + { + Pleroma.Web.ActivityPub.MRF, + [], + [filter: fn o -> {:ok, o} end] + }, + { + Pleroma.Web.ActivityPub.ActivityPub, + [], + [persist: fn o, m -> {:ok, o, m} end] + }, + { + Pleroma.Web.ActivityPub.SideEffects, + [], + [handle: fn o, m -> {:ok, o, m} end] + }, + { + Pleroma.Web.Federator, + [], + [] + } + ]) do + assert {:ok, ^activity, ^meta} = + Pleroma.Web.ActivityPub.Pipeline.common_pipeline(activity, meta) + + assert_called(Pleroma.Web.ActivityPub.ObjectValidator.validate(activity, meta)) + assert_called(Pleroma.Web.ActivityPub.MRF.filter(activity)) + assert_called(Pleroma.Web.ActivityPub.ActivityPub.persist(activity, meta)) + assert_called(Pleroma.Web.ActivityPub.SideEffects.handle(activity, meta)) + end + end + end +end diff --git a/test/web/activity_pub/publisher_test.exs b/test/web/activity_pub/publisher_test.exs index 015af19ab..c2bc38d52 100644 --- a/test/web/activity_pub/publisher_test.exs +++ b/test/web/activity_pub/publisher_test.exs @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.ActivityPub.PublisherTest do @@ -23,6 +23,8 @@ defmodule Pleroma.Web.ActivityPub.PublisherTest do :ok end + setup_all do: clear_config([:instance, :federating], true) + describe "gather_webfinger_links/1" do test "it returns links" do user = insert(:user) @@ -46,10 +48,7 @@ defmodule Pleroma.Web.ActivityPub.PublisherTest do describe "determine_inbox/2" do test "it returns sharedInbox for messages involving as:Public in to" do - user = - insert(:user, %{ - source_data: %{"endpoints" => %{"sharedInbox" => "http://example.com/inbox"}} - }) + user = insert(:user, %{shared_inbox: "http://example.com/inbox"}) activity = %Activity{ data: %{"to" => [@as_public], "cc" => [user.follower_address]} @@ -59,10 +58,7 @@ defmodule Pleroma.Web.ActivityPub.PublisherTest do end test "it returns sharedInbox for messages involving as:Public in cc" do - user = - insert(:user, %{ - source_data: %{"endpoints" => %{"sharedInbox" => "http://example.com/inbox"}} - }) + user = insert(:user, %{shared_inbox: "http://example.com/inbox"}) activity = %Activity{ data: %{"cc" => [@as_public], "to" => [user.follower_address]} @@ -72,11 +68,7 @@ defmodule Pleroma.Web.ActivityPub.PublisherTest do end test "it returns sharedInbox for messages involving multiple recipients in to" do - user = - insert(:user, %{ - source_data: %{"endpoints" => %{"sharedInbox" => "http://example.com/inbox"}} - }) - + user = insert(:user, %{shared_inbox: "http://example.com/inbox"}) user_two = insert(:user) user_three = insert(:user) @@ -88,11 +80,7 @@ defmodule Pleroma.Web.ActivityPub.PublisherTest do end test "it returns sharedInbox for messages involving multiple recipients in cc" do - user = - insert(:user, %{ - source_data: %{"endpoints" => %{"sharedInbox" => "http://example.com/inbox"}} - }) - + user = insert(:user, %{shared_inbox: "http://example.com/inbox"}) user_two = insert(:user) user_three = insert(:user) @@ -105,12 +93,10 @@ defmodule Pleroma.Web.ActivityPub.PublisherTest do test "it returns sharedInbox for messages involving multiple recipients in total" do user = - insert(:user, - source_data: %{ - "inbox" => "http://example.com/personal-inbox", - "endpoints" => %{"sharedInbox" => "http://example.com/inbox"} - } - ) + insert(:user, %{ + shared_inbox: "http://example.com/inbox", + inbox: "http://example.com/personal-inbox" + }) user_two = insert(:user) @@ -123,12 +109,10 @@ defmodule Pleroma.Web.ActivityPub.PublisherTest do test "it returns inbox for messages involving single recipients in total" do user = - insert(:user, - source_data: %{ - "inbox" => "http://example.com/personal-inbox", - "endpoints" => %{"sharedInbox" => "http://example.com/inbox"} - } - ) + insert(:user, %{ + shared_inbox: "http://example.com/inbox", + inbox: "http://example.com/personal-inbox" + }) activity = %Activity{ data: %{"to" => [user.ap_id], "cc" => []} @@ -256,11 +240,11 @@ defmodule Pleroma.Web.ActivityPub.PublisherTest do [:passthrough], [] do follower = - insert(:user, + insert(:user, %{ local: false, - source_data: %{"inbox" => "https://domain.com/users/nick1/inbox"}, + inbox: "https://domain.com/users/nick1/inbox", ap_enabled: true - ) + }) actor = insert(:user, follower_address: follower.ap_id) user = insert(:user) @@ -293,14 +277,14 @@ defmodule Pleroma.Web.ActivityPub.PublisherTest do fetcher = insert(:user, local: false, - source_data: %{"inbox" => "https://domain.com/users/nick1/inbox"}, + inbox: "https://domain.com/users/nick1/inbox", ap_enabled: true ) another_fetcher = insert(:user, local: false, - source_data: %{"inbox" => "https://domain2.com/users/nick1/inbox"}, + inbox: "https://domain2.com/users/nick1/inbox", ap_enabled: true ) diff --git a/test/web/activity_pub/relay_test.exs b/test/web/activity_pub/relay_test.exs index 98dc78f46..9e16e39c4 100644 --- a/test/web/activity_pub/relay_test.exs +++ b/test/web/activity_pub/relay_test.exs @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.ActivityPub.RelayTest do @@ -68,7 +68,7 @@ defmodule Pleroma.Web.ActivityPub.RelayTest do end describe "publish/1" do - clear_config([:instance, :federating]) + setup do: clear_config([:instance, :federating]) test "returns error when activity not `Create` type" do activity = insert(:like_activity) @@ -89,6 +89,11 @@ defmodule Pleroma.Web.ActivityPub.RelayTest do } ) + Tesla.Mock.mock(fn + %{method: :get, url: "http://mastodon.example.org/eee/99541947525187367"} -> + %Tesla.Env{status: 500, body: ""} + end) + assert capture_log(fn -> assert Relay.publish(activity) == {:error, nil} end) =~ "[error] error: nil" diff --git a/test/web/activity_pub/side_effects_test.exs b/test/web/activity_pub/side_effects_test.exs new file mode 100644 index 000000000..797f00d08 --- /dev/null +++ b/test/web/activity_pub/side_effects_test.exs @@ -0,0 +1,267 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ActivityPub.SideEffectsTest do + use Oban.Testing, repo: Pleroma.Repo + use Pleroma.DataCase + + alias Pleroma.Activity + alias Pleroma.Notification + alias Pleroma.Object + alias Pleroma.Repo + alias Pleroma.Tests.ObanHelpers + alias Pleroma.User + alias Pleroma.Web.ActivityPub.ActivityPub + alias Pleroma.Web.ActivityPub.Builder + alias Pleroma.Web.ActivityPub.SideEffects + alias Pleroma.Web.CommonAPI + + import Pleroma.Factory + import Mock + + describe "delete objects" do + setup do + user = insert(:user) + other_user = insert(:user) + + {:ok, op} = CommonAPI.post(other_user, %{status: "big oof"}) + {:ok, post} = CommonAPI.post(user, %{status: "hey", in_reply_to_id: op}) + {:ok, favorite} = CommonAPI.favorite(user, post.id) + object = Object.normalize(post) + {:ok, delete_data, _meta} = Builder.delete(user, object.data["id"]) + {:ok, delete_user_data, _meta} = Builder.delete(user, user.ap_id) + {:ok, delete, _meta} = ActivityPub.persist(delete_data, local: true) + {:ok, delete_user, _meta} = ActivityPub.persist(delete_user_data, local: true) + + %{ + user: user, + delete: delete, + post: post, + object: object, + delete_user: delete_user, + op: op, + favorite: favorite + } + end + + test "it handles object deletions", %{ + delete: delete, + post: post, + object: object, + user: user, + op: op, + favorite: favorite + } do + with_mock Pleroma.Web.ActivityPub.ActivityPub, [:passthrough], + stream_out: fn _ -> nil end, + stream_out_participations: fn _, _ -> nil end do + {:ok, delete, _} = SideEffects.handle(delete) + user = User.get_cached_by_ap_id(object.data["actor"]) + + assert called(Pleroma.Web.ActivityPub.ActivityPub.stream_out(delete)) + assert called(Pleroma.Web.ActivityPub.ActivityPub.stream_out_participations(object, user)) + end + + object = Object.get_by_id(object.id) + assert object.data["type"] == "Tombstone" + refute Activity.get_by_id(post.id) + refute Activity.get_by_id(favorite.id) + + user = User.get_by_id(user.id) + assert user.note_count == 0 + + object = Object.normalize(op.data["object"], false) + + assert object.data["repliesCount"] == 0 + end + + test "it handles object deletions when the object itself has been pruned", %{ + delete: delete, + post: post, + object: object, + user: user, + op: op + } do + with_mock Pleroma.Web.ActivityPub.ActivityPub, [:passthrough], + stream_out: fn _ -> nil end, + stream_out_participations: fn _, _ -> nil end do + {:ok, delete, _} = SideEffects.handle(delete) + user = User.get_cached_by_ap_id(object.data["actor"]) + + assert called(Pleroma.Web.ActivityPub.ActivityPub.stream_out(delete)) + assert called(Pleroma.Web.ActivityPub.ActivityPub.stream_out_participations(object, user)) + end + + object = Object.get_by_id(object.id) + assert object.data["type"] == "Tombstone" + refute Activity.get_by_id(post.id) + + user = User.get_by_id(user.id) + assert user.note_count == 0 + + object = Object.normalize(op.data["object"], false) + + assert object.data["repliesCount"] == 0 + end + + test "it handles user deletions", %{delete_user: delete, user: user} do + {:ok, _delete, _} = SideEffects.handle(delete) + ObanHelpers.perform_all() + + assert User.get_cached_by_ap_id(user.ap_id).deactivated + end + end + + describe "EmojiReact objects" do + setup do + poster = insert(:user) + user = insert(:user) + + {:ok, post} = CommonAPI.post(poster, %{status: "hey"}) + + {:ok, emoji_react_data, []} = Builder.emoji_react(user, post.object, "👌") + {:ok, emoji_react, _meta} = ActivityPub.persist(emoji_react_data, local: true) + + %{emoji_react: emoji_react, user: user, poster: poster} + end + + test "adds the reaction to the object", %{emoji_react: emoji_react, user: user} do + {:ok, emoji_react, _} = SideEffects.handle(emoji_react) + object = Object.get_by_ap_id(emoji_react.data["object"]) + + assert object.data["reaction_count"] == 1 + assert ["👌", [user.ap_id]] in object.data["reactions"] + end + + test "creates a notification", %{emoji_react: emoji_react, poster: poster} do + {:ok, emoji_react, _} = SideEffects.handle(emoji_react) + assert Repo.get_by(Notification, user_id: poster.id, activity_id: emoji_react.id) + end + end + + describe "Undo objects" do + setup do + poster = insert(:user) + user = insert(:user) + {:ok, post} = CommonAPI.post(poster, %{status: "hey"}) + {:ok, like} = CommonAPI.favorite(user, post.id) + {:ok, reaction} = CommonAPI.react_with_emoji(post.id, user, "👍") + {:ok, announce, _} = CommonAPI.repeat(post.id, user) + {:ok, block} = ActivityPub.block(user, poster) + User.block(user, poster) + + {:ok, undo_data, _meta} = Builder.undo(user, like) + {:ok, like_undo, _meta} = ActivityPub.persist(undo_data, local: true) + + {:ok, undo_data, _meta} = Builder.undo(user, reaction) + {:ok, reaction_undo, _meta} = ActivityPub.persist(undo_data, local: true) + + {:ok, undo_data, _meta} = Builder.undo(user, announce) + {:ok, announce_undo, _meta} = ActivityPub.persist(undo_data, local: true) + + {:ok, undo_data, _meta} = Builder.undo(user, block) + {:ok, block_undo, _meta} = ActivityPub.persist(undo_data, local: true) + + %{ + like_undo: like_undo, + post: post, + like: like, + reaction_undo: reaction_undo, + reaction: reaction, + announce_undo: announce_undo, + announce: announce, + block_undo: block_undo, + block: block, + poster: poster, + user: user + } + end + + test "deletes the original block", %{block_undo: block_undo, block: block} do + {:ok, _block_undo, _} = SideEffects.handle(block_undo) + refute Activity.get_by_id(block.id) + end + + test "unblocks the blocked user", %{block_undo: block_undo, block: block} do + blocker = User.get_by_ap_id(block.data["actor"]) + blocked = User.get_by_ap_id(block.data["object"]) + + {:ok, _block_undo, _} = SideEffects.handle(block_undo) + refute User.blocks?(blocker, blocked) + end + + test "an announce undo removes the announce from the object", %{ + announce_undo: announce_undo, + post: post + } do + {:ok, _announce_undo, _} = SideEffects.handle(announce_undo) + + object = Object.get_by_ap_id(post.data["object"]) + + assert object.data["announcement_count"] == 0 + assert object.data["announcements"] == [] + end + + test "deletes the original announce", %{announce_undo: announce_undo, announce: announce} do + {:ok, _announce_undo, _} = SideEffects.handle(announce_undo) + refute Activity.get_by_id(announce.id) + end + + test "a reaction undo removes the reaction from the object", %{ + reaction_undo: reaction_undo, + post: post + } do + {:ok, _reaction_undo, _} = SideEffects.handle(reaction_undo) + + object = Object.get_by_ap_id(post.data["object"]) + + assert object.data["reaction_count"] == 0 + assert object.data["reactions"] == [] + end + + test "deletes the original reaction", %{reaction_undo: reaction_undo, reaction: reaction} do + {:ok, _reaction_undo, _} = SideEffects.handle(reaction_undo) + refute Activity.get_by_id(reaction.id) + end + + test "a like undo removes the like from the object", %{like_undo: like_undo, post: post} do + {:ok, _like_undo, _} = SideEffects.handle(like_undo) + + object = Object.get_by_ap_id(post.data["object"]) + + assert object.data["like_count"] == 0 + assert object.data["likes"] == [] + end + + test "deletes the original like", %{like_undo: like_undo, like: like} do + {:ok, _like_undo, _} = SideEffects.handle(like_undo) + refute Activity.get_by_id(like.id) + end + end + + describe "like objects" do + setup do + poster = insert(:user) + user = insert(:user) + {:ok, post} = CommonAPI.post(poster, %{status: "hey"}) + + {:ok, like_data, _meta} = Builder.like(user, post.object) + {:ok, like, _meta} = ActivityPub.persist(like_data, local: true) + + %{like: like, user: user, poster: poster} + end + + test "add the like to the original object", %{like: like, user: user} do + {:ok, like, _} = SideEffects.handle(like) + object = Object.get_by_ap_id(like.data["object"]) + assert object.data["like_count"] == 1 + assert user.ap_id in object.data["likes"] + end + + test "creates a notification", %{like: like, poster: poster} do + {:ok, like, _} = SideEffects.handle(like) + assert Repo.get_by(Notification, user_id: poster.id, activity_id: like.id) + end + end +end diff --git a/test/web/activity_pub/transmogrifier/delete_handling_test.exs b/test/web/activity_pub/transmogrifier/delete_handling_test.exs new file mode 100644 index 000000000..c9a53918c --- /dev/null +++ b/test/web/activity_pub/transmogrifier/delete_handling_test.exs @@ -0,0 +1,114 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ActivityPub.Transmogrifier.DeleteHandlingTest do + use Oban.Testing, repo: Pleroma.Repo + use Pleroma.DataCase + + alias Pleroma.Activity + alias Pleroma.Object + alias Pleroma.Tests.ObanHelpers + alias Pleroma.User + alias Pleroma.Web.ActivityPub.Transmogrifier + + import Pleroma.Factory + + setup_all do + Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end) + :ok + end + + test "it works for incoming deletes" do + activity = insert(:note_activity) + deleting_user = insert(:user) + + data = + File.read!("test/fixtures/mastodon-delete.json") + |> Poison.decode!() + |> Map.put("actor", deleting_user.ap_id) + |> put_in(["object", "id"], activity.data["object"]) + + {:ok, %Activity{actor: actor, local: false, data: %{"id" => id}}} = + Transmogrifier.handle_incoming(data) + + assert id == data["id"] + + # We delete the Create activity because we base our timelines on it. + # This should be changed after we unify objects and activities + refute Activity.get_by_id(activity.id) + assert actor == deleting_user.ap_id + + # Objects are replaced by a tombstone object. + object = Object.normalize(activity.data["object"]) + assert object.data["type"] == "Tombstone" + end + + test "it works for incoming when the object has been pruned" do + activity = insert(:note_activity) + + {:ok, object} = + Object.normalize(activity.data["object"]) + |> Repo.delete() + + Cachex.del(:object_cache, "object:#{object.data["id"]}") + + deleting_user = insert(:user) + + data = + File.read!("test/fixtures/mastodon-delete.json") + |> Poison.decode!() + |> Map.put("actor", deleting_user.ap_id) + |> put_in(["object", "id"], activity.data["object"]) + + {:ok, %Activity{actor: actor, local: false, data: %{"id" => id}}} = + Transmogrifier.handle_incoming(data) + + assert id == data["id"] + + # We delete the Create activity because we base our timelines on it. + # This should be changed after we unify objects and activities + refute Activity.get_by_id(activity.id) + assert actor == deleting_user.ap_id + end + + test "it fails for incoming deletes with spoofed origin" do + activity = insert(:note_activity) + %{ap_id: ap_id} = insert(:user, ap_id: "https://gensokyo.2hu/users/raymoo") + + data = + File.read!("test/fixtures/mastodon-delete.json") + |> Poison.decode!() + |> Map.put("actor", ap_id) + |> put_in(["object", "id"], activity.data["object"]) + + assert match?({:error, _}, Transmogrifier.handle_incoming(data)) + end + + @tag capture_log: true + test "it works for incoming user deletes" do + %{ap_id: ap_id} = insert(:user, ap_id: "http://mastodon.example.org/users/admin") + + data = + File.read!("test/fixtures/mastodon-delete-user.json") + |> Poison.decode!() + + {:ok, _} = Transmogrifier.handle_incoming(data) + ObanHelpers.perform_all() + + assert User.get_cached_by_ap_id(ap_id).deactivated + end + + test "it fails for incoming user deletes with spoofed origin" do + %{ap_id: ap_id} = insert(:user) + + data = + File.read!("test/fixtures/mastodon-delete-user.json") + |> Poison.decode!() + |> Map.put("actor", ap_id) + + assert match?({:error, _}, Transmogrifier.handle_incoming(data)) + + assert User.get_cached_by_ap_id(ap_id) + end +end diff --git a/test/web/activity_pub/transmogrifier/emoji_react_handling_test.exs b/test/web/activity_pub/transmogrifier/emoji_react_handling_test.exs new file mode 100644 index 000000000..0fb056b50 --- /dev/null +++ b/test/web/activity_pub/transmogrifier/emoji_react_handling_test.exs @@ -0,0 +1,61 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ActivityPub.Transmogrifier.EmojiReactHandlingTest do + use Pleroma.DataCase + + alias Pleroma.Activity + alias Pleroma.Object + alias Pleroma.Web.ActivityPub.Transmogrifier + alias Pleroma.Web.CommonAPI + + import Pleroma.Factory + + test "it works for incoming emoji reactions" do + user = insert(:user) + other_user = insert(:user, local: false) + {:ok, activity} = CommonAPI.post(user, %{status: "hello"}) + + data = + File.read!("test/fixtures/emoji-reaction.json") + |> Poison.decode!() + |> Map.put("object", activity.data["object"]) + |> Map.put("actor", other_user.ap_id) + + {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data) + + assert data["actor"] == other_user.ap_id + assert data["type"] == "EmojiReact" + assert data["id"] == "http://mastodon.example.org/users/admin#reactions/2" + assert data["object"] == activity.data["object"] + assert data["content"] == "👌" + + object = Object.get_by_ap_id(data["object"]) + + assert object.data["reaction_count"] == 1 + assert match?([["👌", _]], object.data["reactions"]) + end + + test "it reject invalid emoji reactions" do + user = insert(:user) + other_user = insert(:user, local: false) + {:ok, activity} = CommonAPI.post(user, %{status: "hello"}) + + data = + File.read!("test/fixtures/emoji-reaction-too-long.json") + |> Poison.decode!() + |> Map.put("object", activity.data["object"]) + |> Map.put("actor", other_user.ap_id) + + assert {:error, _} = Transmogrifier.handle_incoming(data) + + data = + File.read!("test/fixtures/emoji-reaction-no-emoji.json") + |> Poison.decode!() + |> Map.put("object", activity.data["object"]) + |> Map.put("actor", other_user.ap_id) + + assert {:error, _} = Transmogrifier.handle_incoming(data) + end +end diff --git a/test/web/activity_pub/transmogrifier/follow_handling_test.exs b/test/web/activity_pub/transmogrifier/follow_handling_test.exs index fd771ac54..967389fae 100644 --- a/test/web/activity_pub/transmogrifier/follow_handling_test.exs +++ b/test/web/activity_pub/transmogrifier/follow_handling_test.exs @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.ActivityPub.Transmogrifier.FollowHandlingTest do @@ -19,7 +19,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier.FollowHandlingTest do end describe "handle_incoming" do - clear_config([:user, :deny_follow_blocked]) + setup do: clear_config([:user, :deny_follow_blocked]) test "it works for osada follow request" do user = insert(:user) diff --git a/test/web/activity_pub/transmogrifier/like_handling_test.exs b/test/web/activity_pub/transmogrifier/like_handling_test.exs new file mode 100644 index 000000000..53fe1d550 --- /dev/null +++ b/test/web/activity_pub/transmogrifier/like_handling_test.exs @@ -0,0 +1,78 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ActivityPub.Transmogrifier.LikeHandlingTest do + use Pleroma.DataCase + + alias Pleroma.Activity + alias Pleroma.Web.ActivityPub.Transmogrifier + alias Pleroma.Web.CommonAPI + + import Pleroma.Factory + + test "it works for incoming likes" do + user = insert(:user) + + {:ok, activity} = CommonAPI.post(user, %{status: "hello"}) + + data = + File.read!("test/fixtures/mastodon-like.json") + |> Poison.decode!() + |> Map.put("object", activity.data["object"]) + + _actor = insert(:user, ap_id: data["actor"], local: false) + + {:ok, %Activity{data: data, local: false} = activity} = Transmogrifier.handle_incoming(data) + + refute Enum.empty?(activity.recipients) + + assert data["actor"] == "http://mastodon.example.org/users/admin" + assert data["type"] == "Like" + assert data["id"] == "http://mastodon.example.org/users/admin#likes/2" + assert data["object"] == activity.data["object"] + end + + test "it works for incoming misskey likes, turning them into EmojiReacts" do + user = insert(:user) + + {:ok, activity} = CommonAPI.post(user, %{status: "hello"}) + + data = + File.read!("test/fixtures/misskey-like.json") + |> Poison.decode!() + |> Map.put("object", activity.data["object"]) + + _actor = insert(:user, ap_id: data["actor"], local: false) + + {:ok, %Activity{data: activity_data, local: false}} = Transmogrifier.handle_incoming(data) + + assert activity_data["actor"] == data["actor"] + assert activity_data["type"] == "EmojiReact" + assert activity_data["id"] == data["id"] + assert activity_data["object"] == activity.data["object"] + assert activity_data["content"] == "🍮" + end + + test "it works for incoming misskey likes that contain unicode emojis, turning them into EmojiReacts" do + user = insert(:user) + + {:ok, activity} = CommonAPI.post(user, %{status: "hello"}) + + data = + File.read!("test/fixtures/misskey-like.json") + |> Poison.decode!() + |> Map.put("object", activity.data["object"]) + |> Map.put("_misskey_reaction", "⭐") + + _actor = insert(:user, ap_id: data["actor"], local: false) + + {:ok, %Activity{data: activity_data, local: false}} = Transmogrifier.handle_incoming(data) + + assert activity_data["actor"] == data["actor"] + assert activity_data["type"] == "EmojiReact" + assert activity_data["id"] == data["id"] + assert activity_data["object"] == activity.data["object"] + assert activity_data["content"] == "⭐" + end +end diff --git a/test/web/activity_pub/transmogrifier/undo_handling_test.exs b/test/web/activity_pub/transmogrifier/undo_handling_test.exs new file mode 100644 index 000000000..01dd6c370 --- /dev/null +++ b/test/web/activity_pub/transmogrifier/undo_handling_test.exs @@ -0,0 +1,185 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ActivityPub.Transmogrifier.UndoHandlingTest do + use Pleroma.DataCase + + alias Pleroma.Activity + alias Pleroma.Object + alias Pleroma.User + alias Pleroma.Web.ActivityPub.Transmogrifier + alias Pleroma.Web.CommonAPI + + import Pleroma.Factory + + test "it works for incoming emoji reaction undos" do + user = insert(:user) + + {:ok, activity} = CommonAPI.post(user, %{status: "hello"}) + {:ok, reaction_activity} = CommonAPI.react_with_emoji(activity.id, user, "👌") + + data = + File.read!("test/fixtures/mastodon-undo-like.json") + |> Poison.decode!() + |> Map.put("object", reaction_activity.data["id"]) + |> Map.put("actor", user.ap_id) + + {:ok, activity} = Transmogrifier.handle_incoming(data) + + assert activity.actor == user.ap_id + assert activity.data["id"] == data["id"] + assert activity.data["type"] == "Undo" + end + + test "it returns an error for incoming unlikes wihout a like activity" do + user = insert(:user) + {:ok, activity} = CommonAPI.post(user, %{status: "leave a like pls"}) + + data = + File.read!("test/fixtures/mastodon-undo-like.json") + |> Poison.decode!() + |> Map.put("object", activity.data["object"]) + + assert Transmogrifier.handle_incoming(data) == :error + end + + test "it works for incoming unlikes with an existing like activity" do + user = insert(:user) + {:ok, activity} = CommonAPI.post(user, %{status: "leave a like pls"}) + + like_data = + File.read!("test/fixtures/mastodon-like.json") + |> Poison.decode!() + |> Map.put("object", activity.data["object"]) + + _liker = insert(:user, ap_id: like_data["actor"], local: false) + + {:ok, %Activity{data: like_data, local: false}} = Transmogrifier.handle_incoming(like_data) + + data = + File.read!("test/fixtures/mastodon-undo-like.json") + |> Poison.decode!() + |> Map.put("object", like_data) + |> Map.put("actor", like_data["actor"]) + + {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data) + + assert data["actor"] == "http://mastodon.example.org/users/admin" + assert data["type"] == "Undo" + assert data["id"] == "http://mastodon.example.org/users/admin#likes/2/undo" + assert data["object"] == "http://mastodon.example.org/users/admin#likes/2" + + note = Object.get_by_ap_id(like_data["object"]) + assert note.data["like_count"] == 0 + assert note.data["likes"] == [] + end + + test "it works for incoming unlikes with an existing like activity and a compact object" do + user = insert(:user) + {:ok, activity} = CommonAPI.post(user, %{status: "leave a like pls"}) + + like_data = + File.read!("test/fixtures/mastodon-like.json") + |> Poison.decode!() + |> Map.put("object", activity.data["object"]) + + _liker = insert(:user, ap_id: like_data["actor"], local: false) + + {:ok, %Activity{data: like_data, local: false}} = Transmogrifier.handle_incoming(like_data) + + data = + File.read!("test/fixtures/mastodon-undo-like.json") + |> Poison.decode!() + |> Map.put("object", like_data["id"]) + |> Map.put("actor", like_data["actor"]) + + {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data) + + assert data["actor"] == "http://mastodon.example.org/users/admin" + assert data["type"] == "Undo" + assert data["id"] == "http://mastodon.example.org/users/admin#likes/2/undo" + assert data["object"] == "http://mastodon.example.org/users/admin#likes/2" + end + + test "it works for incoming unannounces with an existing notice" do + user = insert(:user) + {:ok, activity} = CommonAPI.post(user, %{status: "hey"}) + + announce_data = + File.read!("test/fixtures/mastodon-announce.json") + |> Poison.decode!() + |> Map.put("object", activity.data["object"]) + + _announcer = insert(:user, ap_id: announce_data["actor"], local: false) + + {:ok, %Activity{data: announce_data, local: false}} = + Transmogrifier.handle_incoming(announce_data) + + data = + File.read!("test/fixtures/mastodon-undo-announce.json") + |> Poison.decode!() + |> Map.put("object", announce_data) + |> Map.put("actor", announce_data["actor"]) + + {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data) + + assert data["type"] == "Undo" + + assert data["object"] == + "http://mastodon.example.org/users/admin/statuses/99542391527669785/activity" + end + + test "it works for incomming unfollows with an existing follow" do + user = insert(:user) + + follow_data = + File.read!("test/fixtures/mastodon-follow-activity.json") + |> Poison.decode!() + |> Map.put("object", user.ap_id) + + _follower = insert(:user, ap_id: follow_data["actor"], local: false) + + {:ok, %Activity{data: _, local: false}} = Transmogrifier.handle_incoming(follow_data) + + data = + File.read!("test/fixtures/mastodon-unfollow-activity.json") + |> Poison.decode!() + |> Map.put("object", follow_data) + + {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data) + + assert data["type"] == "Undo" + assert data["object"]["type"] == "Follow" + assert data["object"]["object"] == user.ap_id + assert data["actor"] == "http://mastodon.example.org/users/admin" + + refute User.following?(User.get_cached_by_ap_id(data["actor"]), user) + end + + test "it works for incoming unblocks with an existing block" do + user = insert(:user) + + block_data = + File.read!("test/fixtures/mastodon-block-activity.json") + |> Poison.decode!() + |> Map.put("object", user.ap_id) + + _blocker = insert(:user, ap_id: block_data["actor"], local: false) + + {:ok, %Activity{data: _, local: false}} = Transmogrifier.handle_incoming(block_data) + + data = + File.read!("test/fixtures/mastodon-unblock-activity.json") + |> Poison.decode!() + |> Map.put("object", block_data) + + {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data) + assert data["type"] == "Undo" + assert data["object"] == block_data["id"] + + blocker = User.get_cached_by_ap_id(data["actor"]) + + refute User.blocks?(blocker, user) + end +end diff --git a/test/web/activity_pub/transmogrifier_test.exs b/test/web/activity_pub/transmogrifier_test.exs index 1b12ee3a9..0a54e3bb9 100644 --- a/test/web/activity_pub/transmogrifier_test.exs +++ b/test/web/activity_pub/transmogrifier_test.exs @@ -1,9 +1,11 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do + use Oban.Testing, repo: Pleroma.Repo use Pleroma.DataCase + alias Pleroma.Activity alias Pleroma.Object alias Pleroma.Object.Fetcher @@ -23,7 +25,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do :ok end - clear_config([:instance, :max_remote_account_fields]) + setup do: clear_config([:instance, :max_remote_account_fields]) describe "handle_incoming" do test "it ignores an incoming notice if we already have it" do @@ -40,7 +42,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do end @tag capture_log: true - test "it fetches replied-to activities if we don't have them" do + test "it fetches reply-to activities if we don't have them" do data = File.read!("test/fixtures/mastodon-post-activity.json") |> Poison.decode!() @@ -61,7 +63,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do assert returned_object.data["inReplyToAtomUri"] == "https://shitposter.club/notice/2827873" end - test "it does not fetch replied-to activities beyond max_replies_depth" do + test "it does not fetch reply-to activities beyond max replies depth limit" do data = File.read!("test/fixtures/mastodon-post-activity.json") |> Poison.decode!() @@ -73,7 +75,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do data = Map.put(data, "object", object) with_mock Pleroma.Web.Federator, - allowed_incoming_reply_depth?: fn _ -> false end do + allowed_thread_distance?: fn _ -> false end do {:ok, returned_activity} = Transmogrifier.handle_incoming(data) returned_object = Object.normalize(returned_activity, false) @@ -210,8 +212,8 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do {:ok, activity} = CommonAPI.post(user, %{ - "status" => "suya...", - "poll" => %{"options" => ["suya", "suya.", "suya.."], "expires_in" => 10} + status: "suya...", + poll: %{options: ["suya", "suya.", "suya.."], expires_in: 10} }) object = Object.normalize(activity) @@ -258,6 +260,24 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do "<p>henlo from my Psion netBook</p><p>message sent from my Psion netBook</p>" end + test "it works for incoming honk announces" do + _user = insert(:user, ap_id: "https://honktest/u/test", local: false) + other_user = insert(:user) + {:ok, post} = CommonAPI.post(other_user, %{status: "bonkeronk"}) + + announce = %{ + "@context" => "https://www.w3.org/ns/activitystreams", + "actor" => "https://honktest/u/test", + "id" => "https://honktest/u/test/bonk/1793M7B9MQ48847vdx", + "object" => post.data["object"], + "published" => "2019-06-25T19:33:58Z", + "to" => "https://www.w3.org/ns/activitystreams#Public", + "type" => "Announce" + } + + {:ok, %Activity{local: false}} = Transmogrifier.handle_incoming(announce) + end + test "it works for incoming announces with actor being inlined (kroeg)" do data = File.read!("test/fixtures/kroeg-announce-with-inline-actor.json") |> Poison.decode!() @@ -323,178 +343,6 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do assert object_data["cc"] == to end - test "it works for incoming likes" do - user = insert(:user) - {:ok, activity} = CommonAPI.post(user, %{"status" => "hello"}) - - data = - File.read!("test/fixtures/mastodon-like.json") - |> Poison.decode!() - |> Map.put("object", activity.data["object"]) - - {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data) - - assert data["actor"] == "http://mastodon.example.org/users/admin" - assert data["type"] == "Like" - assert data["id"] == "http://mastodon.example.org/users/admin#likes/2" - assert data["object"] == activity.data["object"] - end - - test "it works for incoming misskey likes, turning them into EmojiReacts" do - user = insert(:user) - {:ok, activity} = CommonAPI.post(user, %{"status" => "hello"}) - - data = - File.read!("test/fixtures/misskey-like.json") - |> Poison.decode!() - |> Map.put("object", activity.data["object"]) - - {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data) - - assert data["actor"] == data["actor"] - assert data["type"] == "EmojiReact" - assert data["id"] == data["id"] - assert data["object"] == activity.data["object"] - assert data["content"] == "🍮" - end - - test "it works for incoming misskey likes that contain unicode emojis, turning them into EmojiReacts" do - user = insert(:user) - {:ok, activity} = CommonAPI.post(user, %{"status" => "hello"}) - - data = - File.read!("test/fixtures/misskey-like.json") - |> Poison.decode!() - |> Map.put("object", activity.data["object"]) - |> Map.put("_misskey_reaction", "⭐") - - {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data) - - assert data["actor"] == data["actor"] - assert data["type"] == "EmojiReact" - assert data["id"] == data["id"] - assert data["object"] == activity.data["object"] - assert data["content"] == "⭐" - end - - test "it works for incoming emoji reactions" do - user = insert(:user) - {:ok, activity} = CommonAPI.post(user, %{"status" => "hello"}) - - data = - File.read!("test/fixtures/emoji-reaction.json") - |> Poison.decode!() - |> Map.put("object", activity.data["object"]) - - {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data) - - assert data["actor"] == "http://mastodon.example.org/users/admin" - assert data["type"] == "EmojiReact" - assert data["id"] == "http://mastodon.example.org/users/admin#reactions/2" - assert data["object"] == activity.data["object"] - assert data["content"] == "👌" - end - - test "it reject invalid emoji reactions" do - user = insert(:user) - {:ok, activity} = CommonAPI.post(user, %{"status" => "hello"}) - - data = - File.read!("test/fixtures/emoji-reaction-too-long.json") - |> Poison.decode!() - |> Map.put("object", activity.data["object"]) - - assert :error = Transmogrifier.handle_incoming(data) - - data = - File.read!("test/fixtures/emoji-reaction-no-emoji.json") - |> Poison.decode!() - |> Map.put("object", activity.data["object"]) - - assert :error = Transmogrifier.handle_incoming(data) - end - - test "it works for incoming emoji reaction undos" do - user = insert(:user) - - {:ok, activity} = CommonAPI.post(user, %{"status" => "hello"}) - {:ok, reaction_activity, _object} = CommonAPI.react_with_emoji(activity.id, user, "👌") - - data = - File.read!("test/fixtures/mastodon-undo-like.json") - |> Poison.decode!() - |> Map.put("object", reaction_activity.data["id"]) - |> Map.put("actor", user.ap_id) - - {:ok, activity} = Transmogrifier.handle_incoming(data) - - assert activity.actor == user.ap_id - assert activity.data["id"] == data["id"] - assert activity.data["type"] == "Undo" - end - - test "it returns an error for incoming unlikes wihout a like activity" do - user = insert(:user) - {:ok, activity} = CommonAPI.post(user, %{"status" => "leave a like pls"}) - - data = - File.read!("test/fixtures/mastodon-undo-like.json") - |> Poison.decode!() - |> Map.put("object", activity.data["object"]) - - assert Transmogrifier.handle_incoming(data) == :error - end - - test "it works for incoming unlikes with an existing like activity" do - user = insert(:user) - {:ok, activity} = CommonAPI.post(user, %{"status" => "leave a like pls"}) - - like_data = - File.read!("test/fixtures/mastodon-like.json") - |> Poison.decode!() - |> Map.put("object", activity.data["object"]) - - {:ok, %Activity{data: like_data, local: false}} = Transmogrifier.handle_incoming(like_data) - - data = - File.read!("test/fixtures/mastodon-undo-like.json") - |> Poison.decode!() - |> Map.put("object", like_data) - |> Map.put("actor", like_data["actor"]) - - {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data) - - assert data["actor"] == "http://mastodon.example.org/users/admin" - assert data["type"] == "Undo" - assert data["id"] == "http://mastodon.example.org/users/admin#likes/2/undo" - assert data["object"]["id"] == "http://mastodon.example.org/users/admin#likes/2" - end - - test "it works for incoming unlikes with an existing like activity and a compact object" do - user = insert(:user) - {:ok, activity} = CommonAPI.post(user, %{"status" => "leave a like pls"}) - - like_data = - File.read!("test/fixtures/mastodon-like.json") - |> Poison.decode!() - |> Map.put("object", activity.data["object"]) - - {:ok, %Activity{data: like_data, local: false}} = Transmogrifier.handle_incoming(like_data) - - data = - File.read!("test/fixtures/mastodon-undo-like.json") - |> Poison.decode!() - |> Map.put("object", like_data["id"]) - |> Map.put("actor", like_data["actor"]) - - {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data) - - assert data["actor"] == "http://mastodon.example.org/users/admin" - assert data["type"] == "Undo" - assert data["id"] == "http://mastodon.example.org/users/admin#likes/2/undo" - assert data["object"]["id"] == "http://mastodon.example.org/users/admin#likes/2" - end - test "it works for incoming announces" do data = File.read!("test/fixtures/mastodon-announce.json") |> Poison.decode!() @@ -514,7 +362,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do test "it works for incoming announces with an existing activity" do user = insert(:user) - {:ok, activity} = CommonAPI.post(user, %{"status" => "hey"}) + {:ok, activity} = CommonAPI.post(user, %{status: "hey"}) data = File.read!("test/fixtures/mastodon-announce.json") @@ -564,7 +412,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do test "it does not clobber the addressing on announce activities" do user = insert(:user) - {:ok, activity} = CommonAPI.post(user, %{"status" => "hey"}) + {:ok, activity} = CommonAPI.post(user, %{status: "hey"}) data = File.read!("test/fixtures/mastodon-announce.json") @@ -650,8 +498,8 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do test "it strips internal reactions" do user = insert(:user) - {:ok, activity} = CommonAPI.post(user, %{"status" => "#cofe"}) - {:ok, _, _} = CommonAPI.react_with_emoji(activity.id, user, "📢") + {:ok, activity} = CommonAPI.post(user, %{status: "#cofe"}) + {:ok, _} = CommonAPI.react_with_emoji(activity.id, user, "📢") %{object: object} = Activity.get_by_id_with_object(activity.id) assert Map.has_key?(object.data, "reactions") @@ -742,7 +590,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do user = User.get_cached_by_ap_id(activity.actor) - assert User.fields(user) == [ + assert user.fields == [ %{"name" => "foo", "value" => "bar"}, %{"name" => "foo1", "value" => "bar1"} ] @@ -763,7 +611,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do user = User.get_cached_by_ap_id(user.ap_id) - assert User.fields(user) == [ + assert user.fields == [ %{"name" => "foo", "value" => "updated"}, %{"name" => "foo1", "value" => "updated"} ] @@ -781,7 +629,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do user = User.get_cached_by_ap_id(user.ap_id) - assert User.fields(user) == [ + assert user.fields == [ %{"name" => "foo", "value" => "updated"}, %{"name" => "foo1", "value" => "updated"} ] @@ -792,7 +640,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do user = User.get_cached_by_ap_id(user.ap_id) - assert User.fields(user) == [] + assert user.fields == [] end test "it works for incoming update activities which lock the account" do @@ -818,112 +666,6 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do assert user.locked == true end - test "it works for incoming deletes" do - activity = insert(:note_activity) - deleting_user = insert(:user) - - data = - File.read!("test/fixtures/mastodon-delete.json") - |> Poison.decode!() - - object = - data["object"] - |> Map.put("id", activity.data["object"]) - - data = - data - |> Map.put("object", object) - |> Map.put("actor", deleting_user.ap_id) - - {:ok, %Activity{actor: actor, local: false, data: %{"id" => id}}} = - Transmogrifier.handle_incoming(data) - - assert id == data["id"] - refute Activity.get_by_id(activity.id) - assert actor == deleting_user.ap_id - end - - test "it fails for incoming deletes with spoofed origin" do - activity = insert(:note_activity) - - data = - File.read!("test/fixtures/mastodon-delete.json") - |> Poison.decode!() - - object = - data["object"] - |> Map.put("id", activity.data["object"]) - - data = - data - |> Map.put("object", object) - - assert capture_log(fn -> - :error = Transmogrifier.handle_incoming(data) - end) =~ - "[error] Could not decode user at fetch http://mastodon.example.org/users/gargron, {:error, :nxdomain}" - - assert Activity.get_by_id(activity.id) - end - - @tag capture_log: true - test "it works for incoming user deletes" do - %{ap_id: ap_id} = insert(:user, ap_id: "http://mastodon.example.org/users/admin") - - data = - File.read!("test/fixtures/mastodon-delete-user.json") - |> Poison.decode!() - - {:ok, _} = Transmogrifier.handle_incoming(data) - ObanHelpers.perform_all() - - refute User.get_cached_by_ap_id(ap_id) - end - - test "it fails for incoming user deletes with spoofed origin" do - %{ap_id: ap_id} = insert(:user) - - data = - File.read!("test/fixtures/mastodon-delete-user.json") - |> Poison.decode!() - |> Map.put("actor", ap_id) - - assert capture_log(fn -> - assert :error == Transmogrifier.handle_incoming(data) - end) =~ "Object containment failed" - - assert User.get_cached_by_ap_id(ap_id) - end - - test "it works for incoming unannounces with an existing notice" do - user = insert(:user) - {:ok, activity} = CommonAPI.post(user, %{"status" => "hey"}) - - announce_data = - File.read!("test/fixtures/mastodon-announce.json") - |> Poison.decode!() - |> Map.put("object", activity.data["object"]) - - {:ok, %Activity{data: announce_data, local: false}} = - Transmogrifier.handle_incoming(announce_data) - - data = - File.read!("test/fixtures/mastodon-undo-announce.json") - |> Poison.decode!() - |> Map.put("object", announce_data) - |> Map.put("actor", announce_data["actor"]) - - {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data) - - assert data["type"] == "Undo" - assert object_data = data["object"] - assert object_data["type"] == "Announce" - assert object_data["object"] == activity.data["object"] - - assert object_data["id"] == - "http://mastodon.example.org/users/admin/statuses/99542391527669785/activity" - end - test "it works for incomming unfollows with an existing follow" do user = insert(:user) @@ -1018,32 +760,6 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do refute User.following?(blocked, blocker) end - test "it works for incoming unblocks with an existing block" do - user = insert(:user) - - block_data = - File.read!("test/fixtures/mastodon-block-activity.json") - |> Poison.decode!() - |> Map.put("object", user.ap_id) - - {:ok, %Activity{data: _, local: false}} = Transmogrifier.handle_incoming(block_data) - - data = - File.read!("test/fixtures/mastodon-unblock-activity.json") - |> Poison.decode!() - |> Map.put("object", block_data) - - {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data) - assert data["type"] == "Undo" - assert data["object"]["type"] == "Block" - assert data["object"]["object"] == user.ap_id - assert data["actor"] == "http://mastodon.example.org/users/admin" - - blocker = User.get_cached_by_ap_id(data["actor"]) - - refute User.blocks?(blocker, user) - end - test "it works for incoming accepts which were pre-accepted" do follower = insert(:user) followed = insert(:user) @@ -1117,6 +833,12 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do follower = User.get_cached_by_id(follower.id) assert User.following?(follower, followed) == true + + follower = User.get_by_id(follower.id) + assert follower.following_count == 1 + + followed = User.get_by_id(followed.id) + assert followed.follower_count == 1 end test "it fails for incoming accepts which cannot be correlated" do @@ -1217,6 +939,35 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do :error = Transmogrifier.handle_incoming(data) end + test "skip converting the content when it is nil" do + object_id = "https://peertube.social/videos/watch/278d2b7c-0f38-4aaa-afe6-9ecc0c4a34fe" + + {:ok, object} = Fetcher.fetch_and_contain_remote_object_from_id(object_id) + + result = + Pleroma.Web.ActivityPub.Transmogrifier.fix_object(Map.merge(object, %{"content" => nil})) + + assert result["content"] == nil + end + + test "it converts content of object to html" do + object_id = "https://peertube.social/videos/watch/278d2b7c-0f38-4aaa-afe6-9ecc0c4a34fe" + + {:ok, %{"content" => content_markdown}} = + Fetcher.fetch_and_contain_remote_object_from_id(object_id) + + {:ok, %Pleroma.Object{data: %{"content" => content}} = object} = + Fetcher.fetch_object_from_id(object_id) + + assert content_markdown == + "Support this and our other Michigan!/usr/group videos and meetings. Learn more at http://mug.org/membership\n\nTwenty Years in Jail: FreeBSD's Jails, Then and Now\n\nJails started as a limited virtualization system, but over the last two years they've..." + + assert content == + "<p>Support this and our other Michigan!/usr/group videos and meetings. Learn more at <a href=\"http://mug.org/membership\">http://mug.org/membership</a></p><p>Twenty Years in Jail: FreeBSD’s Jails, Then and Now</p><p>Jails started as a limited virtualization system, but over the last two years they’ve…</p>" + + assert object.data["mediaType"] == "text/html" + end + test "it remaps video URLs as attachments if necessary" do {:ok, object} = Fetcher.fetch_object_from_id( @@ -1226,19 +977,13 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do attachment = %{ "type" => "Link", "mediaType" => "video/mp4", - "href" => - "https://peertube.moe/static/webseed/df5f464b-be8d-46fb-ad81-2d4c2d1630e3-480.mp4", - "mimeType" => "video/mp4", - "size" => 5_015_880, "url" => [ %{ "href" => "https://peertube.moe/static/webseed/df5f464b-be8d-46fb-ad81-2d4c2d1630e3-480.mp4", - "mediaType" => "video/mp4", - "type" => "Link" + "mediaType" => "video/mp4" } - ], - "width" => 480 + ] } assert object.data["url"] == @@ -1251,7 +996,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do user = insert(:user) other_user = insert(:user) - {:ok, activity} = CommonAPI.post(user, %{"status" => "test post"}) + {:ok, activity} = CommonAPI.post(user, %{status: "test post"}) object = Object.normalize(activity) note_obj = %{ @@ -1348,11 +1093,100 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do end end + describe "`handle_incoming/2`, Mastodon format `replies` handling" do + setup do: clear_config([:activitypub, :note_replies_output_limit], 5) + setup do: clear_config([:instance, :federation_incoming_replies_max_depth]) + + setup do + data = + "test/fixtures/mastodon-post-activity.json" + |> File.read!() + |> Poison.decode!() + + items = get_in(data, ["object", "replies", "first", "items"]) + assert length(items) > 0 + + %{data: data, items: items} + end + + test "schedules background fetching of `replies` items if max thread depth limit allows", %{ + data: data, + items: items + } do + Pleroma.Config.put([:instance, :federation_incoming_replies_max_depth], 10) + + {:ok, _activity} = Transmogrifier.handle_incoming(data) + + for id <- items do + job_args = %{"op" => "fetch_remote", "id" => id, "depth" => 1} + assert_enqueued(worker: Pleroma.Workers.RemoteFetcherWorker, args: job_args) + end + end + + test "does NOT schedule background fetching of `replies` beyond max thread depth limit allows", + %{data: data} do + Pleroma.Config.put([:instance, :federation_incoming_replies_max_depth], 0) + + {:ok, _activity} = Transmogrifier.handle_incoming(data) + + assert all_enqueued(worker: Pleroma.Workers.RemoteFetcherWorker) == [] + end + end + + describe "`handle_incoming/2`, Pleroma format `replies` handling" do + setup do: clear_config([:activitypub, :note_replies_output_limit], 5) + setup do: clear_config([:instance, :federation_incoming_replies_max_depth]) + + setup do + user = insert(:user) + + {:ok, activity} = CommonAPI.post(user, %{status: "post1"}) + + {:ok, reply1} = + CommonAPI.post(user, %{status: "reply1", in_reply_to_status_id: activity.id}) + + {:ok, reply2} = + CommonAPI.post(user, %{status: "reply2", in_reply_to_status_id: activity.id}) + + replies_uris = Enum.map([reply1, reply2], fn a -> a.object.data["id"] end) + + {:ok, federation_output} = Transmogrifier.prepare_outgoing(activity.data) + + Repo.delete(activity.object) + Repo.delete(activity) + + %{federation_output: federation_output, replies_uris: replies_uris} + end + + test "schedules background fetching of `replies` items if max thread depth limit allows", %{ + federation_output: federation_output, + replies_uris: replies_uris + } do + Pleroma.Config.put([:instance, :federation_incoming_replies_max_depth], 1) + + {:ok, _activity} = Transmogrifier.handle_incoming(federation_output) + + for id <- replies_uris do + job_args = %{"op" => "fetch_remote", "id" => id, "depth" => 1} + assert_enqueued(worker: Pleroma.Workers.RemoteFetcherWorker, args: job_args) + end + end + + test "does NOT schedule background fetching of `replies` beyond max thread depth limit allows", + %{federation_output: federation_output} do + Pleroma.Config.put([:instance, :federation_incoming_replies_max_depth], 0) + + {:ok, _activity} = Transmogrifier.handle_incoming(federation_output) + + assert all_enqueued(worker: Pleroma.Workers.RemoteFetcherWorker) == [] + end + end + describe "prepare outgoing" do test "it inlines private announced objects" do user = insert(:user) - {:ok, activity} = CommonAPI.post(user, %{"status" => "hey", "visibility" => "private"}) + {:ok, activity} = CommonAPI.post(user, %{status: "hey", visibility: "private"}) {:ok, announce_activity, _} = CommonAPI.repeat(activity.id, user) @@ -1367,7 +1201,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do other_user = insert(:user) {:ok, activity} = - CommonAPI.post(user, %{"status" => "hey, @#{other_user.nickname}, how are ya? #2hu"}) + CommonAPI.post(user, %{status: "hey, @#{other_user.nickname}, how are ya? #2hu"}) {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data) object = modified["object"] @@ -1391,7 +1225,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do test "it adds the sensitive property" do user = insert(:user) - {:ok, activity} = CommonAPI.post(user, %{"status" => "#nsfw hey"}) + {:ok, activity} = CommonAPI.post(user, %{status: "#nsfw hey"}) {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data) assert modified["object"]["sensitive"] @@ -1400,7 +1234,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do test "it adds the json-ld context and the conversation property" do user = insert(:user) - {:ok, activity} = CommonAPI.post(user, %{"status" => "hey"}) + {:ok, activity} = CommonAPI.post(user, %{status: "hey"}) {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data) assert modified["@context"] == @@ -1412,7 +1246,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do test "it sets the 'attributedTo' property to the actor of the object if it doesn't have one" do user = insert(:user) - {:ok, activity} = CommonAPI.post(user, %{"status" => "hey"}) + {:ok, activity} = CommonAPI.post(user, %{status: "hey"}) {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data) assert modified["object"]["actor"] == modified["object"]["attributedTo"] @@ -1421,7 +1255,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do test "it strips internal hashtag data" do user = insert(:user) - {:ok, activity} = CommonAPI.post(user, %{"status" => "#2hu"}) + {:ok, activity} = CommonAPI.post(user, %{status: "#2hu"}) expected_tag = %{ "href" => Pleroma.Web.Endpoint.url() <> "/tags/2hu", @@ -1437,7 +1271,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do test "it strips internal fields" do user = insert(:user) - {:ok, activity} = CommonAPI.post(user, %{"status" => "#2hu :firefox:"}) + {:ok, activity} = CommonAPI.post(user, %{status: "#2hu :firefox:"}) {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data) @@ -1469,14 +1303,13 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do user = insert(:user) other_user = insert(:user) - {:ok, activity} = CommonAPI.post(user, %{"status" => "2hu :moominmamma:"}) + {:ok, activity} = CommonAPI.post(user, %{status: "2hu :moominmamma:"}) {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data) assert modified["directMessage"] == false - {:ok, activity} = - CommonAPI.post(user, %{"status" => "@#{other_user.nickname} :moominmamma:"}) + {:ok, activity} = CommonAPI.post(user, %{status: "@#{other_user.nickname} :moominmamma:"}) {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data) @@ -1484,8 +1317,8 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do {:ok, activity} = CommonAPI.post(user, %{ - "status" => "@#{other_user.nickname} :moominmamma:", - "visibility" => "direct" + status: "@#{other_user.nickname} :moominmamma:", + visibility: "direct" }) {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data) @@ -1497,8 +1330,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do user = insert(:user) {:ok, list} = Pleroma.List.create("foo", user) - {:ok, activity} = - CommonAPI.post(user, %{"status" => "foobar", "visibility" => "list:#{list.id}"}) + {:ok, activity} = CommonAPI.post(user, %{status: "foobar", visibility: "list:#{list.id}"}) {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data) @@ -1531,10 +1363,10 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do }) user_two = insert(:user) - Pleroma.FollowingRelationship.follow(user_two, user, "accept") + Pleroma.FollowingRelationship.follow(user_two, user, :follow_accept) - {:ok, activity} = CommonAPI.post(user, %{"status" => "test"}) - {:ok, unrelated_activity} = CommonAPI.post(user_two, %{"status" => "test"}) + {:ok, activity} = CommonAPI.post(user, %{status: "test"}) + {:ok, unrelated_activity} = CommonAPI.post(user_two, %{status: "test"}) assert "http://localhost:4001/users/rye@niu.moe/followers" in activity.recipients user = User.get_cached_by_id(user.id) @@ -1700,8 +1532,8 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do {:ok, poll_activity} = CommonAPI.post(user, %{ - "status" => "suya...", - "poll" => %{"options" => ["suya", "suya.", "suya.."], "expires_in" => 10} + status: "suya...", + poll: %{options: ["suya", "suya.", "suya.."], expires_in: 10} }) poll_object = Object.normalize(poll_activity) @@ -1785,7 +1617,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do end describe "fix_in_reply_to/2" do - clear_config([:instance, :federation_incoming_replies_max_depth]) + setup do: clear_config([:instance, :federation_incoming_replies_max_depth]) setup do data = Poison.decode!(File.read!("test/fixtures/mastodon-post-activity.json")) @@ -1970,11 +1802,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do %{ "mediaType" => "video/mp4", "url" => [ - %{ - "href" => "https://peertube.moe/stat-480.mp4", - "mediaType" => "video/mp4", - "type" => "Link" - } + %{"href" => "https://peertube.moe/stat-480.mp4", "mediaType" => "video/mp4"} ] } ] @@ -1992,23 +1820,13 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do %{ "mediaType" => "video/mp4", "url" => [ - %{ - "href" => "https://pe.er/stat-480.mp4", - "mediaType" => "video/mp4", - "type" => "Link" - } + %{"href" => "https://pe.er/stat-480.mp4", "mediaType" => "video/mp4"} ] }, %{ - "href" => "https://pe.er/stat-480.mp4", "mediaType" => "video/mp4", - "mimeType" => "video/mp4", "url" => [ - %{ - "href" => "https://pe.er/stat-480.mp4", - "mediaType" => "video/mp4", - "type" => "Link" - } + %{"href" => "https://pe.er/stat-480.mp4", "mediaType" => "video/mp4"} ] } ] @@ -2046,4 +1864,60 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do } end end + + describe "set_replies/1" do + setup do: clear_config([:activitypub, :note_replies_output_limit], 2) + + test "returns unmodified object if activity doesn't have self-replies" do + data = Poison.decode!(File.read!("test/fixtures/mastodon-post-activity.json")) + assert Transmogrifier.set_replies(data) == data + end + + test "sets `replies` collection with a limited number of self-replies" do + [user, another_user] = insert_list(2, :user) + + {:ok, %{id: id1} = activity} = CommonAPI.post(user, %{status: "1"}) + + {:ok, %{id: id2} = self_reply1} = + CommonAPI.post(user, %{status: "self-reply 1", in_reply_to_status_id: id1}) + + {:ok, self_reply2} = + CommonAPI.post(user, %{status: "self-reply 2", in_reply_to_status_id: id1}) + + # Assuming to _not_ be present in `replies` due to :note_replies_output_limit is set to 2 + {:ok, _} = CommonAPI.post(user, %{status: "self-reply 3", in_reply_to_status_id: id1}) + + {:ok, _} = + CommonAPI.post(user, %{ + status: "self-reply to self-reply", + in_reply_to_status_id: id2 + }) + + {:ok, _} = + CommonAPI.post(another_user, %{ + status: "another user's reply", + in_reply_to_status_id: id1 + }) + + object = Object.normalize(activity) + replies_uris = Enum.map([self_reply1, self_reply2], fn a -> a.object.data["id"] end) + + assert %{"type" => "Collection", "items" => ^replies_uris} = + Transmogrifier.set_replies(object.data)["replies"] + end + end + + test "take_emoji_tags/1" do + user = insert(:user, %{emoji: %{"firefox" => "https://example.org/firefox.png"}}) + + assert Transmogrifier.take_emoji_tags(user) == [ + %{ + "icon" => %{"type" => "Image", "url" => "https://example.org/firefox.png"}, + "id" => "https://example.org/firefox.png", + "name" => ":firefox:", + "type" => "Emoji", + "updated" => "1970-01-01T00:00:00Z" + } + ] + end end diff --git a/test/web/activity_pub/utils_test.exs b/test/web/activity_pub/utils_test.exs index 211fa6c95..9e0a0f1c4 100644 --- a/test/web/activity_pub/utils_test.exs +++ b/test/web/activity_pub/utils_test.exs @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.ActivityPub.UtilsTest do @@ -102,34 +102,6 @@ defmodule Pleroma.Web.ActivityPub.UtilsTest do end end - describe "make_unlike_data/3" do - test "returns data for unlike activity" do - user = insert(:user) - like_activity = insert(:like_activity, data_attrs: %{"context" => "test context"}) - - object = Object.normalize(like_activity.data["object"]) - - assert Utils.make_unlike_data(user, like_activity, nil) == %{ - "type" => "Undo", - "actor" => user.ap_id, - "object" => like_activity.data, - "to" => [user.follower_address, object.data["actor"]], - "cc" => [Pleroma.Constants.as_public()], - "context" => like_activity.data["context"] - } - - assert Utils.make_unlike_data(user, like_activity, "9mJEZK0tky1w2xD2vY") == %{ - "type" => "Undo", - "actor" => user.ap_id, - "object" => like_activity.data, - "to" => [user.follower_address, object.data["actor"]], - "cc" => [Pleroma.Constants.as_public()], - "context" => like_activity.data["context"], - "id" => "9mJEZK0tky1w2xD2vY" - } - end - end - describe "make_like_data" do setup do user = insert(:user) @@ -148,7 +120,7 @@ defmodule Pleroma.Web.ActivityPub.UtilsTest do {:ok, activity} = CommonAPI.post(user, %{ - "status" => + status: "hey @#{other_user.nickname}, @#{third_user.nickname} how about beering together this weekend?" }) @@ -167,8 +139,8 @@ defmodule Pleroma.Web.ActivityPub.UtilsTest do {:ok, activity} = CommonAPI.post(user, %{ - "status" => "@#{other_user.nickname} @#{third_user.nickname} bought a new swimsuit!", - "visibility" => "private" + status: "@#{other_user.nickname} @#{third_user.nickname} bought a new swimsuit!", + visibility: "private" }) %{"to" => to, "cc" => cc} = Utils.make_like_data(other_user, activity, nil) @@ -177,71 +149,6 @@ defmodule Pleroma.Web.ActivityPub.UtilsTest do end end - describe "fetch_ordered_collection" do - import Tesla.Mock - - test "fetches the first OrderedCollectionPage when an OrderedCollection is encountered" do - mock(fn - %{method: :get, url: "http://mastodon.com/outbox"} -> - json(%{"type" => "OrderedCollection", "first" => "http://mastodon.com/outbox?page=true"}) - - %{method: :get, url: "http://mastodon.com/outbox?page=true"} -> - json(%{"type" => "OrderedCollectionPage", "orderedItems" => ["ok"]}) - end) - - assert Utils.fetch_ordered_collection("http://mastodon.com/outbox", 1) == ["ok"] - end - - test "fetches several pages in the right order one after another, but only the specified amount" do - mock(fn - %{method: :get, url: "http://example.com/outbox"} -> - json(%{ - "type" => "OrderedCollectionPage", - "orderedItems" => [0], - "next" => "http://example.com/outbox?page=1" - }) - - %{method: :get, url: "http://example.com/outbox?page=1"} -> - json(%{ - "type" => "OrderedCollectionPage", - "orderedItems" => [1], - "next" => "http://example.com/outbox?page=2" - }) - - %{method: :get, url: "http://example.com/outbox?page=2"} -> - json(%{"type" => "OrderedCollectionPage", "orderedItems" => [2]}) - end) - - assert Utils.fetch_ordered_collection("http://example.com/outbox", 0) == [0] - assert Utils.fetch_ordered_collection("http://example.com/outbox", 1) == [0, 1] - end - - test "returns an error if the url doesn't have an OrderedCollection/Page" do - mock(fn - %{method: :get, url: "http://example.com/not-an-outbox"} -> - json(%{"type" => "NotAnOutbox"}) - end) - - assert {:error, _} = Utils.fetch_ordered_collection("http://example.com/not-an-outbox", 1) - end - - test "returns the what was collected if there are less pages than specified" do - mock(fn - %{method: :get, url: "http://example.com/outbox"} -> - json(%{ - "type" => "OrderedCollectionPage", - "orderedItems" => [0], - "next" => "http://example.com/outbox?page=1" - }) - - %{method: :get, url: "http://example.com/outbox?page=1"} -> - json(%{"type" => "OrderedCollectionPage", "orderedItems" => [1]}) - end) - - assert Utils.fetch_ordered_collection("http://example.com/outbox", 5) == [0, 1] - end - end - test "make_json_ld_header/0" do assert Utils.make_json_ld_header() == %{ "@context" => [ @@ -261,11 +168,11 @@ defmodule Pleroma.Web.ActivityPub.UtilsTest do {:ok, activity} = CommonAPI.post(user, %{ - "status" => "How do I pronounce LaTeX?", - "poll" => %{ - "options" => ["laytekh", "lahtekh", "latex"], - "expires_in" => 20, - "multiple" => true + status: "How do I pronounce LaTeX?", + poll: %{ + options: ["laytekh", "lahtekh", "latex"], + expires_in: 20, + multiple: true } }) @@ -280,17 +187,16 @@ defmodule Pleroma.Web.ActivityPub.UtilsTest do {:ok, activity} = CommonAPI.post(user, %{ - "status" => "Are we living in a society?", - "poll" => %{ - "options" => ["yes", "no"], - "expires_in" => 20 + status: "Are we living in a society?", + poll: %{ + options: ["yes", "no"], + expires_in: 20 } }) object = Object.normalize(activity) {:ok, [vote], object} = CommonAPI.vote(other_user, object, [0]) - vote_object = Object.normalize(vote) - {:ok, _activity, _object} = ActivityPub.like(user, vote_object) + {:ok, _activity} = CommonAPI.favorite(user, activity.id) [fetched_vote] = Utils.get_existing_votes(other_user.ap_id, object) assert fetched_vote.id == vote.id end @@ -411,7 +317,7 @@ defmodule Pleroma.Web.ActivityPub.UtilsTest do user = insert(:user) refute Utils.get_existing_like(user.ap_id, object) - {:ok, like_activity, _object} = ActivityPub.like(user, object) + {:ok, like_activity} = CommonAPI.favorite(user, note_activity.id) assert ^like_activity = Utils.get_existing_like(user.ap_id, object) end @@ -563,7 +469,7 @@ defmodule Pleroma.Web.ActivityPub.UtilsTest do test "returns map with Flag object" do reporter = insert(:user) target_account = insert(:user) - {:ok, activity} = CommonAPI.post(target_account, %{"status" => "foobar"}) + {:ok, activity} = CommonAPI.post(target_account, %{status: "foobar"}) context = Utils.generate_context_id() content = "foobar" diff --git a/test/web/activity_pub/views/object_view_test.exs b/test/web/activity_pub/views/object_view_test.exs index 13447dc29..43f0617f0 100644 --- a/test/web/activity_pub/views/object_view_test.exs +++ b/test/web/activity_pub/views/object_view_test.exs @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.ActivityPub.ObjectViewTest do @@ -36,12 +36,30 @@ defmodule Pleroma.Web.ActivityPub.ObjectViewTest do assert result["@context"] end + describe "note activity's `replies` collection rendering" do + setup do: clear_config([:activitypub, :note_replies_output_limit], 5) + + test "renders `replies` collection for a note activity" do + user = insert(:user) + activity = insert(:note_activity, user: user) + + {:ok, self_reply1} = + CommonAPI.post(user, %{status: "self-reply 1", in_reply_to_status_id: activity.id}) + + replies_uris = [self_reply1.object.data["id"]] + result = ObjectView.render("object.json", %{object: refresh_record(activity)}) + + assert %{"type" => "Collection", "items" => ^replies_uris} = + get_in(result, ["object", "replies"]) + end + end + test "renders a like activity" do note = insert(:note_activity) object = Object.normalize(note) user = insert(:user) - {:ok, like_activity, _} = CommonAPI.favorite(note.id, user) + {:ok, like_activity} = CommonAPI.favorite(user, note.id) result = ObjectView.render("object.json", %{object: like_activity}) diff --git a/test/web/activity_pub/views/user_view_test.exs b/test/web/activity_pub/views/user_view_test.exs index 8374b8d23..20b0f223c 100644 --- a/test/web/activity_pub/views/user_view_test.exs +++ b/test/web/activity_pub/views/user_view_test.exs @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.ActivityPub.UserViewTest do @@ -29,7 +29,7 @@ defmodule Pleroma.Web.ActivityPub.UserViewTest do {:ok, user} = insert(:user) - |> User.upgrade_changeset(%{fields: fields}) + |> User.update_changeset(%{fields: fields}) |> User.update_and_set_cache() assert %{ @@ -38,7 +38,7 @@ defmodule Pleroma.Web.ActivityPub.UserViewTest do end test "Renders with emoji tags" do - user = insert(:user, emoji: [%{"bib" => "/test"}]) + user = insert(:user, emoji: %{"bib" => "/test"}) assert %{ "tag" => [ @@ -164,7 +164,7 @@ defmodule Pleroma.Web.ActivityPub.UserViewTest do posts = for i <- 0..25 do - {:ok, activity} = CommonAPI.post(user, %{"status" => "post #{i}"}) + {:ok, activity} = CommonAPI.post(user, %{status: "post #{i}"}) activity end diff --git a/test/web/activity_pub/visibilty_test.exs b/test/web/activity_pub/visibilty_test.exs index 4c2e0d207..8e9354c65 100644 --- a/test/web/activity_pub/visibilty_test.exs +++ b/test/web/activity_pub/visibilty_test.exs @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.ActivityPub.VisibilityTest do @@ -21,21 +21,21 @@ defmodule Pleroma.Web.ActivityPub.VisibilityTest do Pleroma.List.follow(list, unrelated) {:ok, public} = - CommonAPI.post(user, %{"status" => "@#{mentioned.nickname}", "visibility" => "public"}) + CommonAPI.post(user, %{status: "@#{mentioned.nickname}", visibility: "public"}) {:ok, private} = - CommonAPI.post(user, %{"status" => "@#{mentioned.nickname}", "visibility" => "private"}) + CommonAPI.post(user, %{status: "@#{mentioned.nickname}", visibility: "private"}) {:ok, direct} = - CommonAPI.post(user, %{"status" => "@#{mentioned.nickname}", "visibility" => "direct"}) + CommonAPI.post(user, %{status: "@#{mentioned.nickname}", visibility: "direct"}) {:ok, unlisted} = - CommonAPI.post(user, %{"status" => "@#{mentioned.nickname}", "visibility" => "unlisted"}) + CommonAPI.post(user, %{status: "@#{mentioned.nickname}", visibility: "unlisted"}) {:ok, list} = CommonAPI.post(user, %{ - "status" => "@#{mentioned.nickname}", - "visibility" => "list:#{list.id}" + status: "@#{mentioned.nickname}", + visibility: "list:#{list.id}" }) %{ |