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}"        })      %{ | 
