diff options
Diffstat (limited to 'test/web')
117 files changed, 14876 insertions, 11298 deletions
diff --git a/test/web/activity_pub/activity_pub_controller_test.exs b/test/web/activity_pub/activity_pub_controller_test.exs index 40344f17e..ba2ce1dd9 100644 --- a/test/web/activity_pub/activity_pub_controller_test.exs +++ b/test/web/activity_pub/activity_pub_controller_test.exs @@ -1,32 +1,37 @@  # Pleroma: A lightweight social networking server -# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do    use Pleroma.Web.ConnCase +  use Oban.Testing, repo: Pleroma.Repo +    import Pleroma.Factory    alias Pleroma.Activity +  alias Pleroma.Delivery    alias Pleroma.Instances    alias Pleroma.Object +  alias Pleroma.Tests.ObanHelpers    alias Pleroma.User    alias Pleroma.Web.ActivityPub.ObjectView +  alias Pleroma.Web.ActivityPub.Relay    alias Pleroma.Web.ActivityPub.UserView    alias Pleroma.Web.ActivityPub.Utils    alias Pleroma.Web.CommonAPI +  alias Pleroma.Workers.ReceiverWorker    setup_all do      Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end) - -    config_path = [:instance, :federating] -    initial_setting = Pleroma.Config.get(config_path) - -    Pleroma.Config.put(config_path, true) -    on_exit(fn -> Pleroma.Config.put(config_path, initial_setting) end) -      :ok    end +  clear_config_all([:instance, :federating], +    do: Pleroma.Config.put([:instance, :federating], true) +  ) +    describe "/relay" do +    clear_config([:instance, :allow_relay]) +      test "with the relay active, it returns the relay user", %{conn: conn} do        res =          conn @@ -43,8 +48,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do        |> get(activity_pub_path(conn, :relay))        |> json_response(404)        |> assert - -      Pleroma.Config.put([:instance, :allow_relay], true)      end    end @@ -107,6 +110,19 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do        assert json_response(conn, 200) == UserView.render("user.json", %{user: user})      end + +    test "it returns 404 for remote users", %{ +      conn: conn +    } do +      user = insert(:user, local: false, nickname: "remoteuser@example.com") + +      conn = +        conn +        |> put_req_header("accept", "application/json") +        |> get("/users/#{user.nickname}.json") + +      assert json_response(conn, 404) +    end    end    describe "/object/:uuid" do @@ -177,21 +193,48 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do        assert json_response(conn, 404)      end -  end -  describe "/object/:uuid/likes" do -    test "it returns the like activities in a collection", %{conn: conn} do -      like = insert(:like_activity) -      like_object_ap_id = Object.normalize(like).data["id"] -      uuid = String.split(like_object_ap_id, "/") |> List.last() +    test "it caches a response", %{conn: conn} do +      note = insert(:note) +      uuid = String.split(note.data["id"], "/") |> List.last() -      result = +      conn1 =          conn          |> put_req_header("accept", "application/activity+json") -        |> get("/objects/#{uuid}/likes") -        |> json_response(200) +        |> get("/objects/#{uuid}") + +      assert json_response(conn1, :ok) +      assert Enum.any?(conn1.resp_headers, &(&1 == {"x-cache", "MISS from Pleroma"})) + +      conn2 = +        conn +        |> put_req_header("accept", "application/activity+json") +        |> get("/objects/#{uuid}") -      assert List.first(result["first"]["orderedItems"])["id"] == like.data["id"] +      assert json_response(conn1, :ok) == json_response(conn2, :ok) +      assert Enum.any?(conn2.resp_headers, &(&1 == {"x-cache", "HIT from Pleroma"})) +    end + +    test "cached purged after object deletion", %{conn: conn} do +      note = insert(:note) +      uuid = String.split(note.data["id"], "/") |> List.last() + +      conn1 = +        conn +        |> put_req_header("accept", "application/activity+json") +        |> get("/objects/#{uuid}") + +      assert json_response(conn1, :ok) +      assert Enum.any?(conn1.resp_headers, &(&1 == {"x-cache", "MISS from Pleroma"})) + +      Object.delete(note) + +      conn2 = +        conn +        |> put_req_header("accept", "application/activity+json") +        |> get("/objects/#{uuid}") + +      assert "Not found" == json_response(conn2, :not_found)      end    end @@ -219,6 +262,51 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do        assert json_response(conn, 404)      end + +    test "it caches a response", %{conn: conn} do +      activity = insert(:note_activity) +      uuid = String.split(activity.data["id"], "/") |> List.last() + +      conn1 = +        conn +        |> put_req_header("accept", "application/activity+json") +        |> get("/activities/#{uuid}") + +      assert json_response(conn1, :ok) +      assert Enum.any?(conn1.resp_headers, &(&1 == {"x-cache", "MISS from Pleroma"})) + +      conn2 = +        conn +        |> put_req_header("accept", "application/activity+json") +        |> get("/activities/#{uuid}") + +      assert json_response(conn1, :ok) == json_response(conn2, :ok) +      assert Enum.any?(conn2.resp_headers, &(&1 == {"x-cache", "HIT from Pleroma"})) +    end + +    test "cached purged after activity deletion", %{conn: conn} do +      user = insert(:user) +      {:ok, activity} = CommonAPI.post(user, %{"status" => "cofe"}) + +      uuid = String.split(activity.data["id"], "/") |> List.last() + +      conn1 = +        conn +        |> put_req_header("accept", "application/activity+json") +        |> get("/activities/#{uuid}") + +      assert json_response(conn1, :ok) +      assert Enum.any?(conn1.resp_headers, &(&1 == {"x-cache", "MISS from Pleroma"})) + +      Activity.delete_all_by_object_ap_id(activity.object.data["id"]) + +      conn2 = +        conn +        |> put_req_header("accept", "application/activity+json") +        |> get("/activities/#{uuid}") + +      assert "Not found" == json_response(conn2, :not_found) +    end    end    describe "/inbox" do @@ -232,7 +320,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do          |> post("/inbox", data)        assert "ok" == json_response(conn, 200) -      :timer.sleep(500) + +      ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))        assert Activity.get_by_ap_id(data["id"])      end @@ -274,10 +363,91 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do          |> post("/users/#{user.nickname}/inbox", data)        assert "ok" == json_response(conn, 200) -      :timer.sleep(500) +      ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))        assert Activity.get_by_ap_id(data["id"])      end +    test "it accepts messages with to as string instead of array", %{conn: conn, data: data} do +      user = insert(:user) + +      data = +        Map.put(data, "to", user.ap_id) +        |> Map.delete("cc") + +      conn = +        conn +        |> assign(:valid_signature, true) +        |> put_req_header("content-type", "application/activity+json") +        |> post("/users/#{user.nickname}/inbox", data) + +      assert "ok" == json_response(conn, 200) +      ObanHelpers.perform(all_enqueued(worker: ReceiverWorker)) +      assert Activity.get_by_ap_id(data["id"]) +    end + +    test "it accepts messages with cc as string instead of array", %{conn: conn, data: data} do +      user = insert(:user) + +      data = +        Map.put(data, "cc", user.ap_id) +        |> Map.delete("to") + +      conn = +        conn +        |> assign(:valid_signature, true) +        |> put_req_header("content-type", "application/activity+json") +        |> post("/users/#{user.nickname}/inbox", data) + +      assert "ok" == json_response(conn, 200) +      ObanHelpers.perform(all_enqueued(worker: ReceiverWorker)) +      %Activity{} = activity = Activity.get_by_ap_id(data["id"]) +      assert user.ap_id in activity.recipients +    end + +    test "it accepts messages with bcc as string instead of array", %{conn: conn, data: data} do +      user = insert(:user) + +      data = +        Map.put(data, "bcc", user.ap_id) +        |> Map.delete("to") +        |> Map.delete("cc") + +      conn = +        conn +        |> assign(:valid_signature, true) +        |> put_req_header("content-type", "application/activity+json") +        |> post("/users/#{user.nickname}/inbox", data) + +      assert "ok" == json_response(conn, 200) +      ObanHelpers.perform(all_enqueued(worker: ReceiverWorker)) +      assert Activity.get_by_ap_id(data["id"]) +    end + +    test "it accepts announces with to as string instead of array", %{conn: conn} do +      user = insert(:user) + +      data = %{ +        "@context" => "https://www.w3.org/ns/activitystreams", +        "actor" => "http://mastodon.example.org/users/admin", +        "id" => "http://mastodon.example.org/users/admin/statuses/19512778738411822/activity", +        "object" => "https://mastodon.social/users/emelie/statuses/101849165031453009", +        "to" => "https://www.w3.org/ns/activitystreams#Public", +        "cc" => [user.ap_id], +        "type" => "Announce" +      } + +      conn = +        conn +        |> assign(:valid_signature, true) +        |> put_req_header("content-type", "application/activity+json") +        |> post("/users/#{user.nickname}/inbox", data) + +      assert "ok" == json_response(conn, 200) +      ObanHelpers.perform(all_enqueued(worker: ReceiverWorker)) +      %Activity{} = activity = Activity.get_by_ap_id(data["id"]) +      assert "https://www.w3.org/ns/activitystreams#Public" in activity.recipients +    end +      test "it accepts messages from actors that are followed by the user", %{        conn: conn,        data: data @@ -303,7 +473,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do          |> post("/users/#{recipient.nickname}/inbox", data)        assert "ok" == json_response(conn, 200) -      :timer.sleep(500) +      ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))        assert Activity.get_by_ap_id(data["id"])      end @@ -320,6 +490,17 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do        assert json_response(conn, 403)      end +    test "it doesn't crash without an authenticated user", %{conn: conn} do +      user = insert(:user) + +      conn = +        conn +        |> put_req_header("accept", "application/activity+json") +        |> get("/users/#{user.nickname}/inbox") + +      assert json_response(conn, 403) +    end +      test "it returns a note activity in a collection", %{conn: conn} do        note_activity = insert(:direct_note_activity)        note_object = Object.normalize(note_activity) @@ -329,7 +510,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do          conn          |> assign(:user, user)          |> put_req_header("accept", "application/activity+json") -        |> get("/users/#{user.nickname}/inbox") +        |> get("/users/#{user.nickname}/inbox?page=true")        assert response(conn, 200) =~ note_object.data["content"]      end @@ -382,6 +563,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do        |> post("/users/#{recipient.nickname}/inbox", data)        |> json_response(200) +      ObanHelpers.perform(all_enqueued(worker: ReceiverWorker)) +        activity = Activity.get_by_ap_id(data["id"])        assert activity.id @@ -415,7 +598,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do        conn =          conn          |> put_req_header("accept", "application/activity+json") -        |> get("/users/#{user.nickname}/outbox") +        |> get("/users/#{user.nickname}/outbox?page=true")        assert response(conn, 200) =~ note_object.data["content"]      end @@ -427,7 +610,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do        conn =          conn          |> put_req_header("accept", "application/activity+json") -        |> get("/users/#{user.nickname}/outbox") +        |> get("/users/#{user.nickname}/outbox?page=true")        assert response(conn, 200) =~ announce_activity.data["object"]      end @@ -457,6 +640,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do          |> post("/users/#{user.nickname}/outbox", data)        result = json_response(conn, 201) +        assert Activity.get_by_ap_id(result["id"])      end @@ -549,6 +733,34 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do      end    end +  describe "/relay/followers" do +    test "it returns relay followers", %{conn: conn} do +      relay_actor = Relay.get_actor() +      user = insert(:user) +      User.follow(user, relay_actor) + +      result = +        conn +        |> assign(:relay, true) +        |> get("/relay/followers") +        |> json_response(200) + +      assert result["first"]["orderedItems"] == [user.ap_id] +    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 +  end +    describe "/users/:nickname/followers" do      test "it returns the followers in a collection", %{conn: conn} do        user = insert(:user) @@ -565,7 +777,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do      test "it returns returns a uri if the user has 'hide_followers' set", %{conn: conn} do        user = insert(:user) -      user_two = insert(:user, %{info: %{hide_followers: true}}) +      user_two = insert(:user, hide_followers: true)        User.follow(user, user_two)        result = @@ -578,7 +790,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do      test "it returns a 403 error on pages, if the user has 'hide_followers' set and the request is not authenticated",           %{conn: conn} do -      user = insert(:user, %{info: %{hide_followers: true}}) +      user = insert(:user, hide_followers: true)        result =          conn @@ -590,7 +802,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do      test "it renders the page, if the user has 'hide_followers' set and the request is authenticated with the same user",           %{conn: conn} do -      user = insert(:user, %{info: %{hide_followers: true}}) +      user = insert(:user, hide_followers: true)        other_user = insert(:user)        {:ok, _other_user, user, _activity} = CommonAPI.follow(other_user, user) @@ -646,7 +858,7 @@ 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, %{info: %{hide_follows: true}}) +      user = insert(:user, hide_follows: true)        user_two = insert(:user)        User.follow(user, user_two) @@ -660,7 +872,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do      test "it returns a 403 error on pages, if the user has 'hide_follows' set and the request is not authenticated",           %{conn: conn} do -      user = insert(:user, %{info: %{hide_follows: true}}) +      user = insert(:user, hide_follows: true)        result =          conn @@ -672,7 +884,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do      test "it renders the page, if the user has 'hide_follows' set and the request is authenticated with the same user",           %{conn: conn} do -      user = insert(:user, %{info: %{hide_follows: true}}) +      user = insert(:user, hide_follows: true)        other_user = insert(:user)        {:ok, user, _other_user, _activity} = CommonAPI.follow(user, other_user) @@ -713,4 +925,126 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do        assert result["totalItems"] == 15      end    end + +  describe "delivery tracking" do +    test "it tracks a signed object fetch", %{conn: conn} do +      user = insert(:user, local: false) +      activity = insert(:note_activity) +      object = Object.normalize(activity) + +      object_path = String.trim_leading(object.data["id"], Pleroma.Web.Endpoint.url()) + +      conn +      |> put_req_header("accept", "application/activity+json") +      |> assign(:user, user) +      |> get(object_path) +      |> json_response(200) + +      assert Delivery.get(object.id, user.id) +    end + +    test "it tracks a signed activity fetch", %{conn: conn} do +      user = insert(:user, local: false) +      activity = insert(:note_activity) +      object = Object.normalize(activity) + +      activity_path = String.trim_leading(activity.data["id"], Pleroma.Web.Endpoint.url()) + +      conn +      |> put_req_header("accept", "application/activity+json") +      |> assign(:user, user) +      |> get(activity_path) +      |> json_response(200) + +      assert Delivery.get(object.id, user.id) +    end + +    test "it tracks a signed object fetch when the json is cached", %{conn: conn} do +      user = insert(:user, local: false) +      other_user = insert(:user, local: false) +      activity = insert(:note_activity) +      object = Object.normalize(activity) + +      object_path = String.trim_leading(object.data["id"], Pleroma.Web.Endpoint.url()) + +      conn +      |> put_req_header("accept", "application/activity+json") +      |> assign(:user, user) +      |> get(object_path) +      |> json_response(200) + +      build_conn() +      |> put_req_header("accept", "application/activity+json") +      |> assign(:user, other_user) +      |> get(object_path) +      |> json_response(200) + +      assert Delivery.get(object.id, user.id) +      assert Delivery.get(object.id, other_user.id) +    end + +    test "it tracks a signed activity fetch when the json is cached", %{conn: conn} do +      user = insert(:user, local: false) +      other_user = insert(:user, local: false) +      activity = insert(:note_activity) +      object = Object.normalize(activity) + +      activity_path = String.trim_leading(activity.data["id"], Pleroma.Web.Endpoint.url()) + +      conn +      |> put_req_header("accept", "application/activity+json") +      |> assign(:user, user) +      |> get(activity_path) +      |> json_response(200) + +      build_conn() +      |> put_req_header("accept", "application/activity+json") +      |> assign(:user, other_user) +      |> get(activity_path) +      |> json_response(200) + +      assert Delivery.get(object.id, user.id) +      assert Delivery.get(object.id, other_user.id) +    end +  end + +  describe "Additionnal ActivityPub C2S endpoints" do +    test "/api/ap/whoami", %{conn: conn} do +      user = insert(:user) + +      conn = +        conn +        |> assign(:user, user) +        |> get("/api/ap/whoami") + +      user = User.get_cached_by_id(user.id) + +      assert UserView.render("user.json", %{user: user}) == json_response(conn, 200) +    end + +    clear_config([:media_proxy]) +    clear_config([Pleroma.Upload]) + +    test "uploadMedia", %{conn: conn} do +      user = insert(:user) + +      desc = "Description of the image" + +      image = %Plug.Upload{ +        content_type: "image/jpg", +        path: Path.absname("test/fixtures/image.jpg"), +        filename: "an_image.jpg" +      } + +      conn = +        conn +        |> assign(:user, user) +        |> post("/api/ap/upload_media", %{"file" => image, "description" => desc}) + +      assert object = json_response(conn, :created) +      assert object["name"] == desc +      assert object["type"] == "Document" +      assert object["actor"] == user.ap_id +    end +  end  end diff --git a/test/web/activity_pub/activity_pub_test.exs b/test/web/activity_pub/activity_pub_test.exs index 00adbc0f9..ff4604a52 100644 --- a/test/web/activity_pub/activity_pub_test.exs +++ b/test/web/activity_pub/activity_pub_test.exs @@ -4,14 +4,16 @@  defmodule Pleroma.Web.ActivityPub.ActivityPubTest do    use Pleroma.DataCase +  use Oban.Testing, repo: Pleroma.Repo +    alias Pleroma.Activity    alias Pleroma.Builders.ActivityBuilder -  alias Pleroma.Instances +  alias Pleroma.Notification    alias Pleroma.Object    alias Pleroma.User    alias Pleroma.Web.ActivityPub.ActivityPub -  alias Pleroma.Web.ActivityPub.Publisher    alias Pleroma.Web.ActivityPub.Utils +  alias Pleroma.Web.AdminAPI.AccountView    alias Pleroma.Web.CommonAPI    import Pleroma.Factory @@ -23,6 +25,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do      :ok    end +  clear_config([:instance, :federating]) +    describe "streaming out participations" do      test "it streams them out" do        user = insert(:user) @@ -38,9 +42,28 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do          stream: fn _, _ -> nil end do          ActivityPub.stream_out_participations(conversation.participations) -        Enum.each(participations, fn participation -> -          assert called(Pleroma.Web.Streamer.stream("participation", participation)) -        end) +        assert called(Pleroma.Web.Streamer.stream("participation", participations)) +      end +    end + +    test "streams them out on activity creation" do +      user_one = insert(:user) +      user_two = insert(:user) + +      with_mock Pleroma.Web.Streamer, +        stream: fn _, _ -> nil end do +        {:ok, activity} = +          CommonAPI.post(user_one, %{ +            "status" => "@#{user_two.nickname}", +            "visibility" => "direct" +          }) + +        conversation = +          activity.data["context"] +          |> Pleroma.Conversation.get_for_ap_id() +          |> Repo.preload(participations: :user) + +        assert called(Pleroma.Web.Streamer.stream("participation", conversation.participations))        end      end    end @@ -89,17 +112,83 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do      end    end +  describe "fetching excluded by visibility" do +    test "it excludes by the appropriate visibility" do +      user = insert(:user) + +      {:ok, public_activity} = CommonAPI.post(user, %{"status" => ".", "visibility" => "public"}) + +      {:ok, direct_activity} = CommonAPI.post(user, %{"status" => ".", "visibility" => "direct"}) + +      {:ok, unlisted_activity} = +        CommonAPI.post(user, %{"status" => ".", "visibility" => "unlisted"}) + +      {:ok, private_activity} = +        CommonAPI.post(user, %{"status" => ".", "visibility" => "private"}) + +      activities = +        ActivityPub.fetch_activities([], %{ +          "exclude_visibilities" => "direct", +          "actor_id" => user.ap_id +        }) + +      assert public_activity in activities +      assert unlisted_activity in activities +      assert private_activity in activities +      refute direct_activity in activities + +      activities = +        ActivityPub.fetch_activities([], %{ +          "exclude_visibilities" => "unlisted", +          "actor_id" => user.ap_id +        }) + +      assert public_activity in activities +      refute unlisted_activity in activities +      assert private_activity in activities +      assert direct_activity in activities + +      activities = +        ActivityPub.fetch_activities([], %{ +          "exclude_visibilities" => "private", +          "actor_id" => user.ap_id +        }) + +      assert public_activity in activities +      assert unlisted_activity in activities +      refute private_activity in activities +      assert direct_activity in activities + +      activities = +        ActivityPub.fetch_activities([], %{ +          "exclude_visibilities" => "public", +          "actor_id" => user.ap_id +        }) + +      refute public_activity in activities +      assert unlisted_activity in activities +      assert private_activity in activities +      assert direct_activity in activities +    end +  end +    describe "building a user from his ap id" do      test "it returns a user" do        user_id = "http://mastodon.example.org/users/admin"        {: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.info.source_data -      assert user.info.ap_enabled +      assert user.source_data +      assert user.ap_enabled        assert user.follower_address == "http://mastodon.example.org/users/admin/followers"      end +    test "it returns a user that is invisible" do +      user_id = "http://mastodon.example.org/users/relay" +      {:ok, user} = ActivityPub.make_user_from_ap_id(user_id) +      assert User.invisible?(user) +    end +      test "it fetches the appropriate tag-restricted posts" do        user = insert(:user) @@ -259,6 +348,42 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do      end    end +  describe "listen activities" do +    test "does not increase user note count" do +      user = insert(:user) + +      {:ok, activity} = +        ActivityPub.listen(%{ +          to: ["https://www.w3.org/ns/activitystreams#Public"], +          actor: user, +          context: "", +          object: %{ +            "actor" => user.ap_id, +            "to" => ["https://www.w3.org/ns/activitystreams#Public"], +            "artist" => "lain", +            "title" => "lain radio episode 1", +            "length" => 180_000, +            "type" => "Audio" +          } +        }) + +      assert activity.actor == user.ap_id + +      user = User.get_cached_by_id(user.id) +      assert user.note_count == 0 +    end + +    test "can be fetched into a timeline" do +      _listen_activity_1 = insert(:listen) +      _listen_activity_2 = insert(:listen) +      _listen_activity_3 = insert(:listen) + +      timeline = ActivityPub.fetch_activities([], %{"type" => ["Listen"]}) + +      assert length(timeline) == 3 +    end +  end +    describe "create activities" do      test "removes doubled 'to' recipients" do        user = insert(:user) @@ -308,7 +433,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do          })        user = User.get_cached_by_id(user.id) -      assert user.info.note_count == 2 +      assert user.note_count == 2      end      test "increases replies count" do @@ -362,7 +487,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do        activity_five = insert(:note_activity)        user = insert(:user) -      {:ok, user} = User.block(user, %{ap_id: activity_five.data["actor"]}) +      {:ok, _user_relationship} = User.block(user, %{ap_id: activity_five.data["actor"]})        activities = ActivityPub.fetch_activities_for_context("2hu", %{"blocking_user" => user})        assert activities == [activity_two, activity] @@ -375,7 +500,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do      activity_three = insert(:note_activity)      user = insert(:user)      booster = insert(:user) -    {:ok, user} = User.block(user, %{ap_id: activity_one.data["actor"]}) +    {:ok, _user_relationship} = User.block(user, %{ap_id: activity_one.data["actor"]})      activities =        ActivityPub.fetch_activities([], %{"blocking_user" => user, "skip_preload" => true}) @@ -384,7 +509,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do      assert Enum.member?(activities, activity_three)      refute Enum.member?(activities, activity_one) -    {:ok, user} = User.unblock(user, %{ap_id: activity_one.data["actor"]}) +    {:ok, _user_block} = User.unblock(user, %{ap_id: activity_one.data["actor"]})      activities =        ActivityPub.fetch_activities([], %{"blocking_user" => user, "skip_preload" => true}) @@ -393,7 +518,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do      assert Enum.member?(activities, activity_three)      assert Enum.member?(activities, activity_one) -    {:ok, user} = User.block(user, %{ap_id: activity_three.data["actor"]}) +    {:ok, _user_relationship} = User.block(user, %{ap_id: activity_three.data["actor"]})      {:ok, _announce, %{data: %{"id" => id}}} = CommonAPI.repeat(activity_three.id, booster)      %Activity{} = boost_activity = Activity.get_create_by_object_ap_id(id)      activity_three = Activity.get_by_id(activity_three.id) @@ -420,7 +545,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do      blockee = insert(:user)      friend = insert(:user) -    {:ok, blocker} = User.block(blocker, blockee) +    {:ok, _user_relationship} = User.block(blocker, blockee)      {:ok, activity_one} = CommonAPI.post(friend, %{"status" => "hey!"}) @@ -443,7 +568,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do      blockee = insert(:user)      friend = insert(:user) -    {:ok, blocker} = User.block(blocker, blockee) +    {:ok, _user_relationship} = User.block(blocker, blockee)      {:ok, activity_one} = CommonAPI.post(friend, %{"status" => "hey!"}) @@ -483,13 +608,48 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do      refute repeat_activity in activities    end +  test "does return activities from followed users on blocked domains" do +    domain = "meanies.social" +    domain_user = insert(:user, %{ap_id: "https://#{domain}/@pundit"}) +    blocker = insert(:user) + +    {:ok, blocker} = User.follow(blocker, domain_user) +    {:ok, blocker} = User.block_domain(blocker, domain) + +    assert User.following?(blocker, domain_user) +    assert User.blocks_domain?(blocker, domain_user) +    refute User.blocks?(blocker, domain_user) + +    note = insert(:note, %{data: %{"actor" => domain_user.ap_id}}) +    activity = insert(:note_activity, %{note: note}) + +    activities = +      ActivityPub.fetch_activities([], %{"blocking_user" => blocker, "skip_preload" => true}) + +    assert activity in activities + +    # And check that if the guy we DO follow boosts someone else from their domain, +    # that should be hidden +    another_user = insert(:user, %{ap_id: "https://#{domain}/@meanie2"}) +    bad_note = insert(:note, %{data: %{"actor" => another_user.ap_id}}) +    bad_activity = insert(:note_activity, %{note: bad_note}) +    {:ok, repeat_activity, _} = CommonAPI.repeat(bad_activity.id, domain_user) + +    activities = +      ActivityPub.fetch_activities([], %{"blocking_user" => blocker, "skip_preload" => true}) + +    refute repeat_activity in activities +  end +    test "doesn't return muted activities" do      activity_one = insert(:note_activity)      activity_two = insert(:note_activity)      activity_three = insert(:note_activity)      user = insert(:user)      booster = insert(:user) -    {:ok, user} = User.mute(user, %User{ap_id: activity_one.data["actor"]}) + +    activity_one_actor = User.get_by_ap_id(activity_one.data["actor"]) +    {:ok, _user_relationships} = User.mute(user, activity_one_actor)      activities =        ActivityPub.fetch_activities([], %{"muting_user" => user, "skip_preload" => true}) @@ -510,7 +670,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do      assert Enum.member?(activities, activity_three)      assert Enum.member?(activities, activity_one) -    {:ok, user} = User.unmute(user, %User{ap_id: activity_one.data["actor"]}) +    {:ok, _user_mute} = User.unmute(user, activity_one_actor)      activities =        ActivityPub.fetch_activities([], %{"muting_user" => user, "skip_preload" => true}) @@ -519,7 +679,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do      assert Enum.member?(activities, activity_three)      assert Enum.member?(activities, activity_one) -    {:ok, user} = User.mute(user, %User{ap_id: activity_three.data["actor"]}) +    activity_three_actor = User.get_by_ap_id(activity_three.data["actor"]) +    {:ok, _user_relationships} = User.mute(user, activity_three_actor)      {:ok, _announce, %{data: %{"id" => id}}} = CommonAPI.repeat(activity_three.id, booster)      %Activity{} = boost_activity = Activity.get_create_by_object_ap_id(id)      activity_three = Activity.get_by_id(activity_three.id) @@ -540,6 +701,29 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do      assert Enum.member?(activities, activity_one)    end +  test "doesn't return thread muted activities" do +    user = insert(:user) +    _activity_one = insert(:note_activity) +    note_two = insert(:note, data: %{"context" => "suya.."}) +    activity_two = insert(:note_activity, note: note_two) + +    {:ok, _activity_two} = CommonAPI.add_mute(user, activity_two) + +    assert [_activity_one] = ActivityPub.fetch_activities([], %{"muting_user" => user}) +  end + +  test "returns thread muted activities when with_muted is set" do +    user = insert(:user) +    _activity_one = insert(:note_activity) +    note_two = insert(:note, data: %{"context" => "suya.."}) +    activity_two = insert(:note_activity, note: note_two) + +    {:ok, _activity_two} = CommonAPI.add_mute(user, activity_two) + +    assert [_activity_two, _activity_one] = +             ActivityPub.fetch_activities([], %{"muting_user" => user, "with_muted" => true}) +  end +    test "does include announces on request" do      activity_three = insert(:note_activity)      user = insert(:user) @@ -549,7 +733,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do      {:ok, announce, _object} = CommonAPI.repeat(activity_three.id, booster) -    [announce_activity] = ActivityPub.fetch_activities([user.ap_id | user.following]) +    [announce_activity] = ActivityPub.fetch_activities([user.ap_id | User.following(user)])      assert announce_activity.id == announce.id    end @@ -589,48 +773,61 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do      end      test "retrieves a maximum of 20 activities" do -      activities = ActivityBuilder.insert_list(30) -      last_expected = List.last(activities) +      ActivityBuilder.insert_list(10) +      expected_activities = ActivityBuilder.insert_list(20)        activities = ActivityPub.fetch_public_activities() -      last = List.last(activities) +      assert collect_ids(activities) == collect_ids(expected_activities)        assert length(activities) == 20 -      assert last == last_expected      end      test "retrieves ids starting from a since_id" do        activities = ActivityBuilder.insert_list(30) -      later_activities = ActivityBuilder.insert_list(10) +      expected_activities = ActivityBuilder.insert_list(10)        since_id = List.last(activities).id -      last_expected = List.last(later_activities)        activities = ActivityPub.fetch_public_activities(%{"since_id" => since_id}) -      last = List.last(activities) +      assert collect_ids(activities) == collect_ids(expected_activities)        assert length(activities) == 10 -      assert last == last_expected      end      test "retrieves ids up to max_id" do -      _first_activities = ActivityBuilder.insert_list(10) -      activities = ActivityBuilder.insert_list(20) -      later_activities = ActivityBuilder.insert_list(10) -      max_id = List.first(later_activities).id -      last_expected = List.last(activities) +      ActivityBuilder.insert_list(10) +      expected_activities = ActivityBuilder.insert_list(20) + +      %{id: max_id} = +        10 +        |> ActivityBuilder.insert_list() +        |> List.first()        activities = ActivityPub.fetch_public_activities(%{"max_id" => max_id}) -      last = List.last(activities)        assert length(activities) == 20 -      assert last == last_expected +      assert collect_ids(activities) == collect_ids(expected_activities) +    end + +    test "paginates via offset/limit" do +      _first_part_activities = ActivityBuilder.insert_list(10) +      second_part_activities = ActivityBuilder.insert_list(10) + +      later_activities = ActivityBuilder.insert_list(10) + +      activities = +        ActivityPub.fetch_public_activities(%{"page" => "2", "page_size" => "20"}, :offset) + +      assert length(activities) == 20 + +      assert collect_ids(activities) == +               collect_ids(second_part_activities) ++ collect_ids(later_activities)      end      test "doesn't return reblogs for users for whom reblogs have been muted" do        activity = insert(:note_activity)        user = insert(:user)        booster = insert(:user) -      {:ok, user} = CommonAPI.hide_reblogs(user, booster) +      {:ok, _reblog_mute} = CommonAPI.hide_reblogs(user, booster)        {:ok, activity, _} = CommonAPI.repeat(activity.id, booster) @@ -643,8 +840,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do        activity = insert(:note_activity)        user = insert(:user)        booster = insert(:user) -      {:ok, user} = CommonAPI.hide_reblogs(user, booster) -      {:ok, user} = CommonAPI.show_reblogs(user, booster) +      {:ok, _reblog_mute} = CommonAPI.hide_reblogs(user, booster) +      {:ok, _reblog_mute} = CommonAPI.show_reblogs(user, booster)        {:ok, activity, _} = CommonAPI.repeat(activity.id, booster) @@ -654,7 +851,118 @@ 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"] == "EmojiReaction" +      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) @@ -679,18 +987,31 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do        assert object.data["likes"] == [user.ap_id]        assert object.data["like_count"] == 1 -      [note_activity] = Activity.get_all_create_by_object_ap_id(object.data["id"]) -      assert note_activity.data["object"]["like_count"] == 1 -        {:ok, _like_activity, object} = ActivityPub.like(user_two, object)        assert object.data["like_count"] == 2 - -      [note_activity] = Activity.get_all_create_by_object_ap_id(object.data["id"]) -      assert note_activity.data["object"]["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) @@ -703,10 +1024,11 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do        {:ok, like_activity, object} = ActivityPub.like(user, object)        assert object.data["like_count"] == 1 -      {:ok, _, _, object} = ActivityPub.unlike(user, object) +      {: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 @@ -731,6 +1053,39 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do      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"}) +      object = Object.normalize(note_activity) + +      {:ok, announce_activity, object} = ActivityPub.announce(user, object, nil, true, false) + +      assert announce_activity.data["to"] == [User.ap_followers(user)] + +      assert announce_activity.data["object"] == object.data["id"] +      assert announce_activity.data["actor"] == user.ap_id +      assert announce_activity.data["context"] == object.data["context"] +    end + +    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"}) +      object = Object.normalize(note_activity) + +      assert {:error, _} = ActivityPub.announce(user, object, nil, true, true) +    end + +    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"}) +      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) @@ -749,7 +1104,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do        assert unannounce_activity.data["to"] == [                 User.ap_followers(user), -               announce_activity.data["actor"] +               object.data["actor"]               ]        assert unannounce_activity.data["type"] == "Undo" @@ -867,7 +1222,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do      end      test "decrements user note count only for public activities" do -      user = insert(:user, info: %{note_count: 10}) +      user = insert(:user, note_count: 10)        {:ok, a1} =          CommonAPI.post(User.get_cached_by_id(user.id), %{ @@ -899,7 +1254,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do        {:ok, _} = Object.normalize(a4) |> ActivityPub.delete()        user = User.get_cached_by_id(user.id) -      assert user.info.note_count == 10 +      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 @@ -953,6 +1308,21 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do        assert %{data: data, object: object} = Activity.get_by_ap_id_with_object(ap_id)        assert object.data["repliesCount"] == 0      end + +    test "it passes delete activity through MRF before deleting the object" do +      rewrite_policy = Pleroma.Config.get([:instance, :rewrite_policy]) +      Pleroma.Config.put([:instance, :rewrite_policy], Pleroma.Web.ActivityPub.MRF.DropPolicy) + +      on_exit(fn -> Pleroma.Config.put([:instance, :rewrite_policy], rewrite_policy) end) + +      note = insert(:note_activity) +      object = Object.normalize(note) + +      {:error, {:reject, _}} = ActivityPub.delete(object) + +      assert Activity.get_by_id(note.id) +      assert Repo.get(Object, object.id).data["type"] == object.data["type"] +    end    end    describe "timeline post-processing" do @@ -990,7 +1360,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do          })        activities = -        ActivityPub.fetch_activities([user1.ap_id | user1.following]) +        ActivityPub.fetch_activities([user1.ap_id | User.following(user1)])          |> Enum.map(fn a -> a.id end)        private_activity_1 = Activity.get_by_ap_id_with_object(private_activity_1.data["id"]) @@ -1000,7 +1370,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do        assert length(activities) == 3        activities = -        ActivityPub.fetch_activities([user1.ap_id | user1.following], %{"user" => user1}) +        ActivityPub.fetch_activities([user1.ap_id | User.following(user1)], %{"user" => user1})          |> Enum.map(fn a -> a.id end)        assert [public_activity.id, private_activity_1.id] == activities @@ -1052,141 +1422,98 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do      assert 3 = length(activities)    end -  test "it can create a Flag activity" do -    reporter = insert(:user) -    target_account = insert(:user) -    {:ok, activity} = CommonAPI.post(target_account, %{"status" => "foobar"}) -    context = Utils.generate_context_id() -    content = "foobar" - -    reporter_ap_id = reporter.ap_id -    target_ap_id = target_account.ap_id -    activity_ap_id = activity.data["id"] - -    assert {:ok, activity} = -             ActivityPub.flag(%{ -               actor: reporter, -               context: context, -               account: target_account, -               statuses: [activity], -               content: content -             }) - -    assert %Activity{ -             actor: ^reporter_ap_id, -             data: %{ -               "type" => "Flag", -               "content" => ^content, -               "context" => ^context, -               "object" => [^target_ap_id, ^activity_ap_id] -             } -           } = activity -  end - -  describe "publish_one/1" do -    test_with_mock "calls `Instances.set_reachable` on successful federation if `unreachable_since` is not specified", -                   Instances, -                   [:passthrough], -                   [] do -      actor = insert(:user) -      inbox = "http://200.site/users/nick1/inbox" - -      assert {:ok, _} = Publisher.publish_one(%{inbox: inbox, json: "{}", actor: actor, id: 1}) - -      assert called(Instances.set_reachable(inbox)) +  describe "flag/1" do +    setup do +      reporter = insert(:user) +      target_account = insert(:user) +      content = "foobar" +      {:ok, activity} = CommonAPI.post(target_account, %{"status" => content}) +      context = Utils.generate_context_id() + +      reporter_ap_id = reporter.ap_id +      target_ap_id = target_account.ap_id +      activity_ap_id = activity.data["id"] + +      activity_with_object = Activity.get_by_ap_id_with_object(activity_ap_id) + +      {:ok, +       %{ +         reporter: reporter, +         context: context, +         target_account: target_account, +         reported_activity: activity, +         content: content, +         activity_ap_id: activity_ap_id, +         activity_with_object: activity_with_object, +         reporter_ap_id: reporter_ap_id, +         target_ap_id: target_ap_id +       }}      end -    test_with_mock "calls `Instances.set_reachable` on successful federation if `unreachable_since` is set", -                   Instances, -                   [:passthrough], -                   [] do -      actor = insert(:user) -      inbox = "http://200.site/users/nick1/inbox" - -      assert {:ok, _} = -               Publisher.publish_one(%{ -                 inbox: inbox, -                 json: "{}", -                 actor: actor, -                 id: 1, -                 unreachable_since: NaiveDateTime.utc_now() +    test "it can create a Flag activity", +         %{ +           reporter: reporter, +           context: context, +           target_account: target_account, +           reported_activity: reported_activity, +           content: content, +           activity_ap_id: activity_ap_id, +           activity_with_object: activity_with_object, +           reporter_ap_id: reporter_ap_id, +           target_ap_id: target_ap_id +         } do +      assert {:ok, activity} = +               ActivityPub.flag(%{ +                 actor: reporter, +                 context: context, +                 account: target_account, +                 statuses: [reported_activity], +                 content: content                 }) -      assert called(Instances.set_reachable(inbox)) -    end - -    test_with_mock "does NOT call `Instances.set_reachable` on successful federation if `unreachable_since` is nil", -                   Instances, -                   [:passthrough], -                   [] do -      actor = insert(:user) -      inbox = "http://200.site/users/nick1/inbox" - -      assert {:ok, _} = -               Publisher.publish_one(%{ -                 inbox: inbox, -                 json: "{}", -                 actor: actor, -                 id: 1, -                 unreachable_since: nil -               }) - -      refute called(Instances.set_reachable(inbox)) -    end - -    test_with_mock "calls `Instances.set_unreachable` on target inbox on non-2xx HTTP response code", -                   Instances, -                   [:passthrough], -                   [] do -      actor = insert(:user) -      inbox = "http://404.site/users/nick1/inbox" - -      assert {:error, _} = Publisher.publish_one(%{inbox: inbox, json: "{}", actor: actor, id: 1}) - -      assert called(Instances.set_unreachable(inbox)) -    end - -    test_with_mock "it calls `Instances.set_unreachable` on target inbox on request error of any kind", -                   Instances, -                   [:passthrough], -                   [] do -      actor = insert(:user) -      inbox = "http://connrefused.site/users/nick1/inbox" - -      assert {:error, _} = Publisher.publish_one(%{inbox: inbox, json: "{}", actor: actor, id: 1}) +      note_obj = %{ +        "type" => "Note", +        "id" => activity_ap_id, +        "content" => content, +        "published" => activity_with_object.object.data["published"], +        "actor" => AccountView.render("show.json", %{user: target_account}) +      } -      assert called(Instances.set_unreachable(inbox)) +      assert %Activity{ +               actor: ^reporter_ap_id, +               data: %{ +                 "type" => "Flag", +                 "content" => ^content, +                 "context" => ^context, +                 "object" => [^target_ap_id, ^note_obj] +               } +             } = activity      end -    test_with_mock "does NOT call `Instances.set_unreachable` if target is reachable", -                   Instances, +    test_with_mock "strips status data from Flag, before federating it", +                   %{ +                     reporter: reporter, +                     context: context, +                     target_account: target_account, +                     reported_activity: reported_activity, +                     content: content +                   }, +                   Utils,                     [:passthrough],                     [] do -      actor = insert(:user) -      inbox = "http://200.site/users/nick1/inbox" +      {:ok, activity} = +        ActivityPub.flag(%{ +          actor: reporter, +          context: context, +          account: target_account, +          statuses: [reported_activity], +          content: content +        }) -      assert {:ok, _} = Publisher.publish_one(%{inbox: inbox, json: "{}", actor: actor, id: 1}) +      new_data = +        put_in(activity.data, ["object"], [target_account.ap_id, reported_activity.data["id"]]) -      refute called(Instances.set_unreachable(inbox)) -    end - -    test_with_mock "does NOT call `Instances.set_unreachable` if target instance has non-nil `unreachable_since`", -                   Instances, -                   [:passthrough], -                   [] do -      actor = insert(:user) -      inbox = "http://connrefused.site/users/nick1/inbox" - -      assert {:error, _} = -               Publisher.publish_one(%{ -                 inbox: inbox, -                 json: "{}", -                 actor: actor, -                 id: 1, -                 unreachable_since: NaiveDateTime.utc_now() -               }) - -      refute called(Instances.set_unreachable(inbox)) +      assert_called(Utils.maybe_federate(%{activity | data: new_data}))      end    end @@ -1237,4 +1564,207 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do        assert result.id == activity.id      end    end + +  describe "fetch_follow_information_for_user" do +    test "syncronizes following/followers counters" do +      user = +        insert(:user, +          local: false, +          follower_address: "http://localhost:4001/users/fuser2/followers", +          following_address: "http://localhost:4001/users/fuser2/following" +        ) + +      {:ok, info} = ActivityPub.fetch_follow_information_for_user(user) +      assert info.follower_count == 527 +      assert info.following_count == 267 +    end + +    test "detects hidden followers" do +      mock(fn env -> +        case env.url do +          "http://localhost:4001/users/masto_closed/followers?page=1" -> +            %Tesla.Env{status: 403, body: ""} + +          _ -> +            apply(HttpRequestMock, :request, [env]) +        end +      end) + +      user = +        insert(:user, +          local: false, +          follower_address: "http://localhost:4001/users/masto_closed/followers", +          following_address: "http://localhost:4001/users/masto_closed/following" +        ) + +      {:ok, follow_info} = ActivityPub.fetch_follow_information_for_user(user) +      assert follow_info.hide_followers == true +      assert follow_info.hide_follows == false +    end + +    test "detects hidden follows" do +      mock(fn env -> +        case env.url do +          "http://localhost:4001/users/masto_closed/following?page=1" -> +            %Tesla.Env{status: 403, body: ""} + +          _ -> +            apply(HttpRequestMock, :request, [env]) +        end +      end) + +      user = +        insert(:user, +          local: false, +          follower_address: "http://localhost:4001/users/masto_closed/followers", +          following_address: "http://localhost:4001/users/masto_closed/following" +        ) + +      {:ok, follow_info} = ActivityPub.fetch_follow_information_for_user(user) +      assert follow_info.hide_followers == false +      assert follow_info.hide_follows == true +    end + +    test "detects hidden follows/followers for friendica" do +      user = +        insert(:user, +          local: false, +          follower_address: "http://localhost:8080/followers/fuser3", +          following_address: "http://localhost:8080/following/fuser3" +        ) + +      {:ok, follow_info} = ActivityPub.fetch_follow_information_for_user(user) +      assert follow_info.hide_followers == true +      assert follow_info.follower_count == 296 +      assert follow_info.following_count == 32 +      assert follow_info.hide_follows == true +    end + +    test "doesn't crash when follower and following counters are hidden" do +      mock(fn env -> +        case env.url do +          "http://localhost:4001/users/masto_hidden_counters/following" -> +            json(%{ +              "@context" => "https://www.w3.org/ns/activitystreams", +              "id" => "http://localhost:4001/users/masto_hidden_counters/followers" +            }) + +          "http://localhost:4001/users/masto_hidden_counters/following?page=1" -> +            %Tesla.Env{status: 403, body: ""} + +          "http://localhost:4001/users/masto_hidden_counters/followers" -> +            json(%{ +              "@context" => "https://www.w3.org/ns/activitystreams", +              "id" => "http://localhost:4001/users/masto_hidden_counters/following" +            }) + +          "http://localhost:4001/users/masto_hidden_counters/followers?page=1" -> +            %Tesla.Env{status: 403, body: ""} +        end +      end) + +      user = +        insert(:user, +          local: false, +          follower_address: "http://localhost:4001/users/masto_hidden_counters/followers", +          following_address: "http://localhost:4001/users/masto_hidden_counters/following" +        ) + +      {:ok, follow_info} = ActivityPub.fetch_follow_information_for_user(user) + +      assert follow_info.hide_followers == true +      assert follow_info.follower_count == 0 +      assert follow_info.hide_follows == true +      assert follow_info.following_count == 0 +    end +  end + +  describe "fetch_favourites/3" do +    test "returns a favourite activities sorted by adds to favorite" do +      user = insert(:user) +      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) +      result = ActivityPub.fetch_favourites(user) + +      assert Enum.map(result, & &1.id) == [a1.id, a5.id, a3.id, a4.id] + +      result = ActivityPub.fetch_favourites(user, %{"limit" => 2}) +      assert Enum.map(result, & &1.id) == [a1.id, a5.id] +    end +  end + +  describe "Move activity" do +    test "create" do +      %{ap_id: old_ap_id} = old_user = insert(:user) +      %{ap_id: new_ap_id} = new_user = insert(:user, also_known_as: [old_ap_id]) +      follower = insert(:user) +      follower_move_opted_out = insert(:user, allow_following_move: false) + +      User.follow(follower, old_user) +      User.follow(follower_move_opted_out, old_user) + +      assert User.following?(follower, old_user) +      assert User.following?(follower_move_opted_out, old_user) + +      assert {:ok, activity} = ActivityPub.move(old_user, new_user) + +      assert %Activity{ +               actor: ^old_ap_id, +               data: %{ +                 "actor" => ^old_ap_id, +                 "object" => ^old_ap_id, +                 "target" => ^new_ap_id, +                 "type" => "Move" +               }, +               local: true +             } = activity + +      params = %{ +        "op" => "move_following", +        "origin_id" => old_user.id, +        "target_id" => new_user.id +      } + +      assert_enqueued(worker: Pleroma.Workers.BackgroundWorker, args: params) + +      Pleroma.Workers.BackgroundWorker.perform(params, nil) + +      refute User.following?(follower, old_user) +      assert User.following?(follower, new_user) + +      assert User.following?(follower_move_opted_out, old_user) +      refute User.following?(follower_move_opted_out, new_user) + +      activity = %Activity{activity | object: nil} + +      assert [%Notification{activity: ^activity}] = +               Notification.for_user(follower, %{with_move: true}) + +      assert [%Notification{activity: ^activity}] = +               Notification.for_user(follower_move_opted_out, %{with_move: true}) +    end + +    test "old user must be in the new user's `also_known_as` list" do +      old_user = insert(:user) +      new_user = insert(:user) + +      assert {:error, "Target account must have the origin in `alsoKnownAs`"} = +               ActivityPub.move(old_user, new_user) +    end +  end  end 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 03dc299ec..b524fdd23 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 @@ -35,7 +35,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.AntiLinkSpamPolicyTest do      test "it allows posts without links" do        user = insert(:user) -      assert user.info.note_count == 0 +      assert user.note_count == 0        message =          @linkless_message @@ -47,7 +47,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.AntiLinkSpamPolicyTest do      test "it disallows posts with links" do        user = insert(:user) -      assert user.info.note_count == 0 +      assert user.note_count == 0        message =          @linkful_message @@ -59,9 +59,9 @@ defmodule Pleroma.Web.ActivityPub.MRF.AntiLinkSpamPolicyTest do    describe "with old user" do      test "it allows posts without links" do -      user = insert(:user, info: %{note_count: 1}) +      user = insert(:user, note_count: 1) -      assert user.info.note_count == 1 +      assert user.note_count == 1        message =          @linkless_message @@ -71,9 +71,9 @@ defmodule Pleroma.Web.ActivityPub.MRF.AntiLinkSpamPolicyTest do      end      test "it allows posts with links" do -      user = insert(:user, info: %{note_count: 1}) +      user = insert(:user, note_count: 1) -      assert user.info.note_count == 1 +      assert user.note_count == 1        message =          @linkful_message @@ -85,9 +85,9 @@ defmodule Pleroma.Web.ActivityPub.MRF.AntiLinkSpamPolicyTest do    describe "with followed new user" do      test "it allows posts without links" do -      user = insert(:user, info: %{follower_count: 1}) +      user = insert(:user, follower_count: 1) -      assert user.info.follower_count == 1 +      assert user.follower_count == 1        message =          @linkless_message @@ -97,9 +97,9 @@ defmodule Pleroma.Web.ActivityPub.MRF.AntiLinkSpamPolicyTest do      end      test "it allows posts with links" do -      user = insert(:user, info: %{follower_count: 1}) +      user = insert(:user, follower_count: 1) -      assert user.info.follower_count == 1 +      assert user.follower_count == 1        message =          @linkful_message @@ -133,7 +133,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.AntiLinkSpamPolicyTest do    describe "with contentless-objects" do      test "it does not reject them or error out" do -      user = insert(:user, info: %{note_count: 1}) +      user = insert(:user, note_count: 1)        message =          @response_message 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 372e789be..95a809d25 100644 --- a/test/web/activity_pub/mrf/mediaproxy_warming_policy_test.exs +++ b/test/web/activity_pub/mrf/mediaproxy_warming_policy_test.exs @@ -6,6 +6,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicyTest do    use Pleroma.DataCase    alias Pleroma.HTTP +  alias Pleroma.Tests.ObanHelpers    alias Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy    import Mock @@ -24,6 +25,11 @@ defmodule Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicyTest do    test "it prefetches media proxy URIs" do      with_mock HTTP, get: fn _, _, _ -> {:ok, []} end do        MediaProxyWarmingPolicy.filter(@message) + +      ObanHelpers.perform_all() +      # Performing jobs which has been just enqueued +      ObanHelpers.perform_all() +        assert called(HTTP.get(:_, :_, :_))      end    end diff --git a/test/web/activity_pub/mrf/mrf_test.exs b/test/web/activity_pub/mrf/mrf_test.exs new file mode 100644 index 000000000..04709df17 --- /dev/null +++ b/test/web/activity_pub/mrf/mrf_test.exs @@ -0,0 +1,86 @@ +defmodule Pleroma.Web.ActivityPub.MRFTest do +  use ExUnit.Case, async: true +  use Pleroma.Tests.Helpers +  alias Pleroma.Web.ActivityPub.MRF + +  test "subdomains_regex/1" do +    assert MRF.subdomains_regex(["unsafe.tld", "*.unsafe.tld"]) == [ +             ~r/^unsafe.tld$/i, +             ~r/^(.*\.)*unsafe.tld$/i +           ] +  end + +  describe "subdomain_match/2" do +    test "common domains" do +      regexes = MRF.subdomains_regex(["unsafe.tld", "unsafe2.tld"]) + +      assert regexes == [~r/^unsafe.tld$/i, ~r/^unsafe2.tld$/i] + +      assert MRF.subdomain_match?(regexes, "unsafe.tld") +      assert MRF.subdomain_match?(regexes, "unsafe2.tld") + +      refute MRF.subdomain_match?(regexes, "example.com") +    end + +    test "wildcard domains with one subdomain" do +      regexes = MRF.subdomains_regex(["*.unsafe.tld"]) + +      assert regexes == [~r/^(.*\.)*unsafe.tld$/i] + +      assert MRF.subdomain_match?(regexes, "unsafe.tld") +      assert MRF.subdomain_match?(regexes, "sub.unsafe.tld") +      refute MRF.subdomain_match?(regexes, "anotherunsafe.tld") +      refute MRF.subdomain_match?(regexes, "unsafe.tldanother") +    end + +    test "wildcard domains with two subdomains" do +      regexes = MRF.subdomains_regex(["*.unsafe.tld"]) + +      assert regexes == [~r/^(.*\.)*unsafe.tld$/i] + +      assert MRF.subdomain_match?(regexes, "unsafe.tld") +      assert MRF.subdomain_match?(regexes, "sub.sub.unsafe.tld") +      refute MRF.subdomain_match?(regexes, "sub.anotherunsafe.tld") +      refute MRF.subdomain_match?(regexes, "sub.unsafe.tldanother") +    end + +    test "matches are case-insensitive" do +      regexes = MRF.subdomains_regex(["UnSafe.TLD", "UnSAFE2.Tld"]) + +      assert regexes == [~r/^UnSafe.TLD$/i, ~r/^UnSAFE2.Tld$/i] + +      assert MRF.subdomain_match?(regexes, "UNSAFE.TLD") +      assert MRF.subdomain_match?(regexes, "UNSAFE2.TLD") +      assert MRF.subdomain_match?(regexes, "unsafe.tld") +      assert MRF.subdomain_match?(regexes, "unsafe2.tld") + +      refute MRF.subdomain_match?(regexes, "EXAMPLE.COM") +      refute MRF.subdomain_match?(regexes, "example.com") +    end +  end + +  describe "describe/0" do +    clear_config([:instance, :rewrite_policy]) + +    test "it works as expected with noop policy" do +      expected = %{ +        mrf_policies: ["NoOpPolicy"], +        exclusions: false +      } + +      {:ok, ^expected} = MRF.describe() +    end + +    test "it works as expected with mock policy" do +      Pleroma.Config.put([:instance, :rewrite_policy], [MRFModuleMock]) + +      expected = %{ +        mrf_policies: ["MRFModuleMock"], +        mrf_module_mock: "some config data", +        exclusions: false +      } + +      {:ok, ^expected} = MRF.describe() +    end +  end +end diff --git a/test/web/activity_pub/mrf/normalize_markup_test.exs b/test/web/activity_pub/mrf/normalize_markup_test.exs index 3916a1f35..0207be56b 100644 --- a/test/web/activity_pub/mrf/normalize_markup_test.exs +++ b/test/web/activity_pub/mrf/normalize_markup_test.exs @@ -20,11 +20,11 @@ defmodule Pleroma.Web.ActivityPub.MRF.NormalizeMarkupTest do      expected = """      <b>this is in bold</b>      <p>this is a paragraph</p> -    this is a linebreak<br /> -    this is a link with allowed "rel" attribute: <a href="http://example.com/" rel="tag">example.com</a> -    this is a link with not allowed "rel" attribute: <a href="http://example.com/">example.com</a> -    this is an image: <img src="http://example.com/image.jpg" /><br /> -    alert('hacked') +    this is a linebreak<br/> +    this is a link with allowed "rel" attribute: <a href="http://example.com/" rel="tag">example.com</a> +    this is a link with not allowed "rel" attribute: <a href="http://example.com/">example.com</a> +    this is an image: <img src="http://example.com/image.jpg"/><br/> +    alert('hacked')      """      message = %{"type" => "Create", "object" => %{"content" => @html_sample}} diff --git a/test/web/activity_pub/mrf/object_age_policy_test.exs b/test/web/activity_pub/mrf/object_age_policy_test.exs new file mode 100644 index 000000000..643609da4 --- /dev/null +++ b/test/web/activity_pub/mrf/object_age_policy_test.exs @@ -0,0 +1,105 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2019 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ActivityPub.MRF.ObjectAgePolicyTest do +  use Pleroma.DataCase +  alias Pleroma.Config +  alias Pleroma.User +  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_all do +    Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end) +    :ok +  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!() + +      {: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()) + +      {:ok, _} = ObjectAgePolicy.filter(data) +    end +  end + +  describe "with delist action" 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!() + +      {:ok, _u} = User.get_or_fetch_by_ap_id(data["actor"]) + +      {:ok, data} = ObjectAgePolicy.filter(data) + +      assert Visibility.get_visibility(%{data: data}) == "unlisted" +    end + +    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()) + +      {:ok, _user} = User.get_or_fetch_by_ap_id(data["actor"]) + +      {:ok, ^data} = ObjectAgePolicy.filter(data) +    end +  end + +  describe "with strip_followers action" 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!() + +      {:ok, user} = User.get_or_fetch_by_ap_id(data["actor"]) + +      {:ok, data} = ObjectAgePolicy.filter(data) + +      refute user.follower_address in data["to"] +      refute user.follower_address in data["cc"] +    end + +    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()) + +      {:ok, _u} = User.get_or_fetch_by_ap_id(data["actor"]) + +      {: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 fdf6b245e..fc1d190bb 100644 --- a/test/web/activity_pub/mrf/reject_non_public_test.exs +++ b/test/web/activity_pub/mrf/reject_non_public_test.exs @@ -8,12 +8,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.RejectNonPublicTest do    alias Pleroma.Web.ActivityPub.MRF.RejectNonPublic -  setup do -    policy = Pleroma.Config.get([:mrf_rejectnonpublic]) -    on_exit(fn -> Pleroma.Config.put([:mrf_rejectnonpublic], policy) end) - -    :ok -  end +  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 0fd68e103..df0f223f8 100644 --- a/test/web/activity_pub/mrf/simple_policy_test.exs +++ b/test/web/activity_pub/mrf/simple_policy_test.exs @@ -8,9 +8,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicyTest do    alias Pleroma.Config    alias Pleroma.Web.ActivityPub.MRF.SimplePolicy -  setup do -    orig = Config.get!(:mrf_simple) - +  clear_config([:mrf_simple]) do      Config.put(:mrf_simple,        media_removal: [],        media_nsfw: [], @@ -21,10 +19,6 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicyTest do        avatar_removal: [],        banner_removal: []      ) - -    on_exit(fn -> -      Config.put(:mrf_simple, orig) -    end)    end    describe "when :media_removal" do @@ -49,6 +43,19 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicyTest do        assert SimplePolicy.filter(local_message) == {:ok, local_message}      end + +    test "match with wildcard domain" do +      Config.put([:mrf_simple, :media_removal], ["*.remote.instance"]) +      media_message = build_media_message() +      local_message = build_local_message() + +      assert SimplePolicy.filter(media_message) == +               {:ok, +                media_message +                |> Map.put("object", Map.delete(media_message["object"], "attachment"))} + +      assert SimplePolicy.filter(local_message) == {:ok, local_message} +    end    end    describe "when :media_nsfw" do @@ -74,6 +81,20 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicyTest do        assert SimplePolicy.filter(local_message) == {:ok, local_message}      end + +    test "match with wildcard domain" do +      Config.put([:mrf_simple, :media_nsfw], ["*.remote.instance"]) +      media_message = build_media_message() +      local_message = build_local_message() + +      assert SimplePolicy.filter(media_message) == +               {:ok, +                media_message +                |> put_in(["object", "tag"], ["foo", "nsfw"]) +                |> put_in(["object", "sensitive"], true)} + +      assert SimplePolicy.filter(local_message) == {:ok, local_message} +    end    end    defp build_media_message do @@ -106,6 +127,15 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicyTest do        assert SimplePolicy.filter(report_message) == {:reject, nil}        assert SimplePolicy.filter(local_message) == {:ok, local_message}      end + +    test "match with wildcard domain" do +      Config.put([:mrf_simple, :report_removal], ["*.remote.instance"]) +      report_message = build_report_message() +      local_message = build_local_message() + +      assert SimplePolicy.filter(report_message) == {:reject, nil} +      assert SimplePolicy.filter(local_message) == {:ok, local_message} +    end    end    defp build_report_message do @@ -146,6 +176,27 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicyTest do        assert SimplePolicy.filter(local_message) == {:ok, local_message}      end +    test "match with wildcard domain" do +      {actor, ftl_message} = build_ftl_actor_and_message() + +      ftl_message_actor_host = +        ftl_message +        |> Map.fetch!("actor") +        |> URI.parse() +        |> Map.fetch!(:host) + +      Config.put([:mrf_simple, :federated_timeline_removal], ["*." <> ftl_message_actor_host]) +      local_message = build_local_message() + +      assert {:ok, ftl_message} = SimplePolicy.filter(ftl_message) +      assert actor.follower_address in ftl_message["to"] +      refute actor.follower_address in ftl_message["cc"] +      refute "https://www.w3.org/ns/activitystreams#Public" in ftl_message["to"] +      assert "https://www.w3.org/ns/activitystreams#Public" in ftl_message["cc"] + +      assert SimplePolicy.filter(local_message) == {:ok, local_message} +    end +      test "has a matching host but only as:Public in to" do        {_actor, ftl_message} = build_ftl_actor_and_message() @@ -185,13 +236,29 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicyTest do        assert SimplePolicy.filter(remote_message) == {:ok, remote_message}      end -    test "has a matching host" do +    test "activity has a matching host" do        Config.put([:mrf_simple, :reject], ["remote.instance"])        remote_message = build_remote_message()        assert SimplePolicy.filter(remote_message) == {:reject, nil}      end + +    test "activity matches with wildcard domain" do +      Config.put([:mrf_simple, :reject], ["*.remote.instance"]) + +      remote_message = build_remote_message() + +      assert SimplePolicy.filter(remote_message) == {:reject, nil} +    end + +    test "actor has a matching host" do +      Config.put([:mrf_simple, :reject], ["remote.instance"]) + +      remote_user = build_remote_user() + +      assert SimplePolicy.filter(remote_user) == {:reject, nil} +    end    end    describe "when :accept" do @@ -205,7 +272,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicyTest do        assert SimplePolicy.filter(remote_message) == {:ok, remote_message}      end -    test "is not empty but it doesn't have a matching host" do +    test "is not empty but activity doesn't have a matching host" do        Config.put([:mrf_simple, :accept], ["non.matching.remote"])        local_message = build_local_message() @@ -215,7 +282,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicyTest do        assert SimplePolicy.filter(remote_message) == {:reject, nil}      end -    test "has a matching host" do +    test "activity has a matching host" do        Config.put([:mrf_simple, :accept], ["remote.instance"])        local_message = build_local_message() @@ -224,6 +291,24 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicyTest do        assert SimplePolicy.filter(local_message) == {:ok, local_message}        assert SimplePolicy.filter(remote_message) == {:ok, remote_message}      end + +    test "activity matches with wildcard domain" do +      Config.put([:mrf_simple, :accept], ["*.remote.instance"]) + +      local_message = build_local_message() +      remote_message = build_remote_message() + +      assert SimplePolicy.filter(local_message) == {:ok, local_message} +      assert SimplePolicy.filter(remote_message) == {:ok, remote_message} +    end + +    test "actor has a matching host" do +      Config.put([:mrf_simple, :accept], ["remote.instance"]) + +      remote_user = build_remote_user() + +      assert SimplePolicy.filter(remote_user) == {:ok, remote_user} +    end    end    describe "when :avatar_removal" do @@ -251,6 +336,15 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicyTest do        refute filtered["icon"]      end + +    test "match with wildcard domain" do +      Config.put([:mrf_simple, :avatar_removal], ["*.remote.instance"]) + +      remote_user = build_remote_user() +      {:ok, filtered} = SimplePolicy.filter(remote_user) + +      refute filtered["icon"] +    end    end    describe "when :banner_removal" do @@ -278,6 +372,15 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicyTest do        refute filtered["image"]      end + +    test "match with wildcard domain" do +      Config.put([:mrf_simple, :banner_removal], ["*.remote.instance"]) + +      remote_user = build_remote_user() +      {:ok, filtered} = SimplePolicy.filter(remote_user) + +      refute filtered["image"] +    end    end    defp build_local_message 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 6519e2398..72084c0fd 100644 --- a/test/web/activity_pub/mrf/user_allowlist_policy_test.exs +++ b/test/web/activity_pub/mrf/user_allowlist_policy_test.exs @@ -7,12 +7,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.UserAllowListPolicyTest do    alias Pleroma.Web.ActivityPub.MRF.UserAllowListPolicy -  setup do -    policy = Pleroma.Config.get([:mrf_user_allowlist]) || [] -    on_exit(fn -> Pleroma.Config.put([:mrf_user_allowlist], policy) end) - -    :ok -  end +  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 new file mode 100644 index 000000000..38309f9f1 --- /dev/null +++ b/test/web/activity_pub/mrf/vocabulary_policy_test.exs @@ -0,0 +1,106 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ActivityPub.MRF.VocabularyPolicyTest do +  use Pleroma.DataCase + +  alias Pleroma.Web.ActivityPub.MRF.VocabularyPolicy + +  describe "accept" do +    clear_config([:mrf_vocabulary, :accept]) + +    test "it accepts based on parent activity type" do +      Pleroma.Config.put([:mrf_vocabulary, :accept], ["Like"]) + +      message = %{ +        "type" => "Like", +        "object" => "whatever" +      } + +      {:ok, ^message} = VocabularyPolicy.filter(message) +    end + +    test "it accepts based on child object type" do +      Pleroma.Config.put([:mrf_vocabulary, :accept], ["Create", "Note"]) + +      message = %{ +        "type" => "Create", +        "object" => %{ +          "type" => "Note", +          "content" => "whatever" +        } +      } + +      {:ok, ^message} = VocabularyPolicy.filter(message) +    end + +    test "it does not accept disallowed child objects" do +      Pleroma.Config.put([:mrf_vocabulary, :accept], ["Create", "Note"]) + +      message = %{ +        "type" => "Create", +        "object" => %{ +          "type" => "Article", +          "content" => "whatever" +        } +      } + +      {:reject, nil} = VocabularyPolicy.filter(message) +    end + +    test "it does not accept disallowed parent types" do +      Pleroma.Config.put([:mrf_vocabulary, :accept], ["Announce", "Note"]) + +      message = %{ +        "type" => "Create", +        "object" => %{ +          "type" => "Note", +          "content" => "whatever" +        } +      } + +      {:reject, nil} = VocabularyPolicy.filter(message) +    end +  end + +  describe "reject" do +    clear_config([:mrf_vocabulary, :reject]) + +    test "it rejects based on parent activity type" do +      Pleroma.Config.put([:mrf_vocabulary, :reject], ["Like"]) + +      message = %{ +        "type" => "Like", +        "object" => "whatever" +      } + +      {:reject, nil} = VocabularyPolicy.filter(message) +    end + +    test "it rejects based on child object type" do +      Pleroma.Config.put([:mrf_vocabulary, :reject], ["Note"]) + +      message = %{ +        "type" => "Create", +        "object" => %{ +          "type" => "Note", +          "content" => "whatever" +        } +      } + +      {:reject, nil} = VocabularyPolicy.filter(message) +    end + +    test "it passes through objects that aren't disallowed" do +      Pleroma.Config.put([:mrf_vocabulary, :reject], ["Like"]) + +      message = %{ +        "type" => "Announce", +        "object" => "whatever" +      } + +      {:ok, ^message} = VocabularyPolicy.filter(message) +    end +  end +end diff --git a/test/web/activity_pub/publisher_test.exs b/test/web/activity_pub/publisher_test.exs new file mode 100644 index 000000000..015af19ab --- /dev/null +++ b/test/web/activity_pub/publisher_test.exs @@ -0,0 +1,349 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2019 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ActivityPub.PublisherTest do +  use Pleroma.Web.ConnCase + +  import ExUnit.CaptureLog +  import Pleroma.Factory +  import Tesla.Mock +  import Mock + +  alias Pleroma.Activity +  alias Pleroma.Instances +  alias Pleroma.Object +  alias Pleroma.Web.ActivityPub.Publisher +  alias Pleroma.Web.CommonAPI + +  @as_public "https://www.w3.org/ns/activitystreams#Public" + +  setup do +    mock(fn env -> apply(HttpRequestMock, :request, [env]) end) +    :ok +  end + +  describe "gather_webfinger_links/1" do +    test "it returns links" do +      user = insert(:user) + +      expected_links = [ +        %{"href" => user.ap_id, "rel" => "self", "type" => "application/activity+json"}, +        %{ +          "href" => user.ap_id, +          "rel" => "self", +          "type" => "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"" +        }, +        %{ +          "rel" => "http://ostatus.org/schema/1.0/subscribe", +          "template" => "#{Pleroma.Web.base_url()}/ostatus_subscribe?acct={uri}" +        } +      ] + +      assert expected_links == Publisher.gather_webfinger_links(user) +    end +  end + +  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"}} +        }) + +      activity = %Activity{ +        data: %{"to" => [@as_public], "cc" => [user.follower_address]} +      } + +      assert Publisher.determine_inbox(activity, user) == "http://example.com/inbox" +    end + +    test "it returns sharedInbox for messages involving as:Public in cc" do +      user = +        insert(:user, %{ +          source_data: %{"endpoints" => %{"sharedInbox" => "http://example.com/inbox"}} +        }) + +      activity = %Activity{ +        data: %{"cc" => [@as_public], "to" => [user.follower_address]} +      } + +      assert Publisher.determine_inbox(activity, user) == "http://example.com/inbox" +    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_two = insert(:user) +      user_three = insert(:user) + +      activity = %Activity{ +        data: %{"cc" => [], "to" => [user.ap_id, user_two.ap_id, user_three.ap_id]} +      } + +      assert Publisher.determine_inbox(activity, user) == "http://example.com/inbox" +    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_two = insert(:user) +      user_three = insert(:user) + +      activity = %Activity{ +        data: %{"to" => [], "cc" => [user.ap_id, user_two.ap_id, user_three.ap_id]} +      } + +      assert Publisher.determine_inbox(activity, user) == "http://example.com/inbox" +    end + +    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"} +          } +        ) + +      user_two = insert(:user) + +      activity = %Activity{ +        data: %{"to" => [user_two.ap_id], "cc" => [user.ap_id]} +      } + +      assert Publisher.determine_inbox(activity, user) == "http://example.com/inbox" +    end + +    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"} +          } +        ) + +      activity = %Activity{ +        data: %{"to" => [user.ap_id], "cc" => []} +      } + +      assert Publisher.determine_inbox(activity, user) == "http://example.com/personal-inbox" +    end +  end + +  describe "publish_one/1" do +    test_with_mock "calls `Instances.set_reachable` on successful federation if `unreachable_since` is not specified", +                   Instances, +                   [:passthrough], +                   [] do +      actor = insert(:user) +      inbox = "http://200.site/users/nick1/inbox" + +      assert {:ok, _} = Publisher.publish_one(%{inbox: inbox, json: "{}", actor: actor, id: 1}) + +      assert called(Instances.set_reachable(inbox)) +    end + +    test_with_mock "calls `Instances.set_reachable` on successful federation if `unreachable_since` is set", +                   Instances, +                   [:passthrough], +                   [] do +      actor = insert(:user) +      inbox = "http://200.site/users/nick1/inbox" + +      assert {:ok, _} = +               Publisher.publish_one(%{ +                 inbox: inbox, +                 json: "{}", +                 actor: actor, +                 id: 1, +                 unreachable_since: NaiveDateTime.utc_now() +               }) + +      assert called(Instances.set_reachable(inbox)) +    end + +    test_with_mock "does NOT call `Instances.set_reachable` on successful federation if `unreachable_since` is nil", +                   Instances, +                   [:passthrough], +                   [] do +      actor = insert(:user) +      inbox = "http://200.site/users/nick1/inbox" + +      assert {:ok, _} = +               Publisher.publish_one(%{ +                 inbox: inbox, +                 json: "{}", +                 actor: actor, +                 id: 1, +                 unreachable_since: nil +               }) + +      refute called(Instances.set_reachable(inbox)) +    end + +    test_with_mock "calls `Instances.set_unreachable` on target inbox on non-2xx HTTP response code", +                   Instances, +                   [:passthrough], +                   [] do +      actor = insert(:user) +      inbox = "http://404.site/users/nick1/inbox" + +      assert {:error, _} = Publisher.publish_one(%{inbox: inbox, json: "{}", actor: actor, id: 1}) + +      assert called(Instances.set_unreachable(inbox)) +    end + +    test_with_mock "it calls `Instances.set_unreachable` on target inbox on request error of any kind", +                   Instances, +                   [:passthrough], +                   [] do +      actor = insert(:user) +      inbox = "http://connrefused.site/users/nick1/inbox" + +      assert capture_log(fn -> +               assert {:error, _} = +                        Publisher.publish_one(%{inbox: inbox, json: "{}", actor: actor, id: 1}) +             end) =~ "connrefused" + +      assert called(Instances.set_unreachable(inbox)) +    end + +    test_with_mock "does NOT call `Instances.set_unreachable` if target is reachable", +                   Instances, +                   [:passthrough], +                   [] do +      actor = insert(:user) +      inbox = "http://200.site/users/nick1/inbox" + +      assert {:ok, _} = Publisher.publish_one(%{inbox: inbox, json: "{}", actor: actor, id: 1}) + +      refute called(Instances.set_unreachable(inbox)) +    end + +    test_with_mock "does NOT call `Instances.set_unreachable` if target instance has non-nil `unreachable_since`", +                   Instances, +                   [:passthrough], +                   [] do +      actor = insert(:user) +      inbox = "http://connrefused.site/users/nick1/inbox" + +      assert capture_log(fn -> +               assert {:error, _} = +                        Publisher.publish_one(%{ +                          inbox: inbox, +                          json: "{}", +                          actor: actor, +                          id: 1, +                          unreachable_since: NaiveDateTime.utc_now() +                        }) +             end) =~ "connrefused" + +      refute called(Instances.set_unreachable(inbox)) +    end +  end + +  describe "publish/2" do +    test_with_mock "publishes an activity with BCC to all relevant peers.", +                   Pleroma.Web.Federator.Publisher, +                   [:passthrough], +                   [] do +      follower = +        insert(:user, +          local: false, +          source_data: %{"inbox" => "https://domain.com/users/nick1/inbox"}, +          ap_enabled: true +        ) + +      actor = insert(:user, follower_address: follower.ap_id) +      user = insert(:user) + +      {:ok, _follower_one} = Pleroma.User.follow(follower, actor) +      actor = refresh_record(actor) + +      note_activity = +        insert(:note_activity, +          recipients: [follower.ap_id], +          data_attrs: %{"bcc" => [user.ap_id]} +        ) + +      res = Publisher.publish(actor, note_activity) +      assert res == :ok + +      assert called( +               Pleroma.Web.Federator.Publisher.enqueue_one(Publisher, %{ +                 inbox: "https://domain.com/users/nick1/inbox", +                 actor_id: actor.id, +                 id: note_activity.data["id"] +               }) +             ) +    end + +    test_with_mock "publishes a delete activity to peers who signed fetch requests to the create acitvity/object.", +                   Pleroma.Web.Federator.Publisher, +                   [:passthrough], +                   [] do +      fetcher = +        insert(:user, +          local: false, +          source_data: %{"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"}, +          ap_enabled: true +        ) + +      actor = insert(:user) + +      note_activity = insert(:note_activity, user: actor) +      object = Object.normalize(note_activity) + +      activity_path = String.trim_leading(note_activity.data["id"], Pleroma.Web.Endpoint.url()) +      object_path = String.trim_leading(object.data["id"], Pleroma.Web.Endpoint.url()) + +      build_conn() +      |> put_req_header("accept", "application/activity+json") +      |> assign(:user, fetcher) +      |> get(object_path) +      |> json_response(200) + +      build_conn() +      |> put_req_header("accept", "application/activity+json") +      |> assign(:user, another_fetcher) +      |> get(activity_path) +      |> json_response(200) + +      {:ok, delete} = CommonAPI.delete(note_activity.id, actor) + +      res = Publisher.publish(actor, delete) +      assert res == :ok + +      assert called( +               Pleroma.Web.Federator.Publisher.enqueue_one(Publisher, %{ +                 inbox: "https://domain.com/users/nick1/inbox", +                 actor_id: actor.id, +                 id: delete.data["id"] +               }) +             ) + +      assert called( +               Pleroma.Web.Federator.Publisher.enqueue_one(Publisher, %{ +                 inbox: "https://domain2.com/users/nick1/inbox", +                 actor_id: actor.id, +                 id: delete.data["id"] +               }) +             ) +    end +  end +end diff --git a/test/web/activity_pub/relay_test.exs b/test/web/activity_pub/relay_test.exs index 21a63c493..98dc78f46 100644 --- a/test/web/activity_pub/relay_test.exs +++ b/test/web/activity_pub/relay_test.exs @@ -1,15 +1,125 @@  # Pleroma: A lightweight social networking server -# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.Web.ActivityPub.RelayTest do    use Pleroma.DataCase +  alias Pleroma.Activity +  alias Pleroma.Object +  alias Pleroma.User +  alias Pleroma.Web.ActivityPub.ActivityPub    alias Pleroma.Web.ActivityPub.Relay +  import ExUnit.CaptureLog +  import Pleroma.Factory +  import Mock +    test "gets an actor for the relay" do      user = Relay.get_actor() +    assert user.ap_id == "#{Pleroma.Web.Endpoint.url()}/relay" +  end + +  test "relay actor is invisible" do +    user = Relay.get_actor() +    assert User.invisible?(user) +  end + +  describe "follow/1" do +    test "returns errors when user not found" do +      assert capture_log(fn -> +               {:error, _} = Relay.follow("test-ap-id") +             end) =~ "Could not decode user at fetch" +    end + +    test "returns activity" do +      user = insert(:user) +      service_actor = Relay.get_actor() +      assert {:ok, %Activity{} = activity} = Relay.follow(user.ap_id) +      assert activity.actor == "#{Pleroma.Web.Endpoint.url()}/relay" +      assert user.ap_id in activity.recipients +      assert activity.data["type"] == "Follow" +      assert activity.data["actor"] == service_actor.ap_id +      assert activity.data["object"] == user.ap_id +    end +  end + +  describe "unfollow/1" do +    test "returns errors when user not found" do +      assert capture_log(fn -> +               {:error, _} = Relay.unfollow("test-ap-id") +             end) =~ "Could not decode user at fetch" +    end + +    test "returns activity" do +      user = insert(:user) +      service_actor = Relay.get_actor() +      ActivityPub.follow(service_actor, user) +      Pleroma.User.follow(service_actor, user) +      assert "#{user.ap_id}/followers" in User.following(service_actor) +      assert {:ok, %Activity{} = activity} = Relay.unfollow(user.ap_id) +      assert activity.actor == "#{Pleroma.Web.Endpoint.url()}/relay" +      assert user.ap_id in activity.recipients +      assert activity.data["type"] == "Undo" +      assert activity.data["actor"] == service_actor.ap_id +      assert activity.data["to"] == [user.ap_id] +      refute "#{user.ap_id}/followers" in User.following(service_actor) +    end +  end + +  describe "publish/1" do +    clear_config([:instance, :federating]) + +    test "returns error when activity not `Create` type" do +      activity = insert(:like_activity) +      assert Relay.publish(activity) == {:error, "Not implemented"} +    end + +    test "returns error when activity not public" do +      activity = insert(:direct_note_activity) +      assert Relay.publish(activity) == {:error, false} +    end + +    test "returns error when object is unknown" do +      activity = +        insert(:note_activity, +          data: %{ +            "type" => "Create", +            "object" => "http://mastodon.example.org/eee/99541947525187367" +          } +        ) + +      assert capture_log(fn -> +               assert Relay.publish(activity) == {:error, nil} +             end) =~ "[error] error: nil" +    end + +    test_with_mock "returns announce activity and publish to federate", +                   Pleroma.Web.Federator, +                   [:passthrough], +                   [] do +      Pleroma.Config.put([:instance, :federating], true) +      service_actor = Relay.get_actor() +      note = insert(:note_activity) +      assert {:ok, %Activity{} = activity, %Object{} = obj} = Relay.publish(note) +      assert activity.data["type"] == "Announce" +      assert activity.data["actor"] == service_actor.ap_id +      assert activity.data["object"] == obj.data["id"] +      assert called(Pleroma.Web.Federator.publish(activity)) +    end -    assert user.ap_id =~ "/relay" +    test_with_mock "returns announce activity and not publish to federate", +                   Pleroma.Web.Federator, +                   [:passthrough], +                   [] do +      Pleroma.Config.put([:instance, :federating], false) +      service_actor = Relay.get_actor() +      note = insert(:note_activity) +      assert {:ok, %Activity{} = activity, %Object{} = obj} = Relay.publish(note) +      assert activity.data["type"] == "Announce" +      assert activity.data["actor"] == service_actor.ap_id +      assert activity.data["object"] == obj.data["id"] +      refute called(Pleroma.Web.Federator.publish(activity)) +    end    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 857d65564..1c88b05c2 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-2018 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.Web.ActivityPub.Transmogrifier.FollowHandlingTest do @@ -19,6 +19,25 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier.FollowHandlingTest do    end    describe "handle_incoming" do +    test "it works for osada follow request" do +      user = insert(:user) + +      data = +        File.read!("test/fixtures/osada-follow-activity.json") +        |> Poison.decode!() +        |> Map.put("object", user.ap_id) + +      {:ok, %Activity{data: data, local: false} = activity} = Transmogrifier.handle_incoming(data) + +      assert data["actor"] == "https://apfed.club/channel/indio" +      assert data["type"] == "Follow" +      assert data["id"] == "https://apfed.club/follow/9" + +      activity = Repo.get(Activity, activity.id) +      assert activity.data["state"] == "accept" +      assert User.following?(User.get_cached_by_ap_id(data["actor"]), user) +    end +      test "it works for incoming follow requests" do        user = insert(:user) @@ -39,7 +58,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier.FollowHandlingTest do      end      test "with locked accounts, it does not create a follow or an accept" do -      user = insert(:user, info: %{locked: true}) +      user = insert(:user, locked: true)        data =          File.read!("test/fixtures/mastodon-follow-activity.json") @@ -59,7 +78,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier.FollowHandlingTest do          )          |> Repo.all() -      assert length(accepts) == 0 +      assert Enum.empty?(accepts)      end      test "it works for follow requests when you are already followed, creating a new accept activity" do @@ -109,7 +128,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier.FollowHandlingTest do        user = insert(:user)        {:ok, target} = User.get_or_fetch("http://mastodon.example.org/users/admin") -      {:ok, user} = User.block(user, target) +      {:ok, _user_relationship} = User.block(user, target)        data =          File.read!("test/fixtures/mastodon-follow-activity.json") diff --git a/test/web/activity_pub/transmogrifier_test.exs b/test/web/activity_pub/transmogrifier_test.exs index a1f5f6e36..5da358c43 100644 --- a/test/web/activity_pub/transmogrifier_test.exs +++ b/test/web/activity_pub/transmogrifier_test.exs @@ -1,5 +1,5 @@  # Pleroma: A lightweight social networking server -# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do @@ -7,13 +7,12 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do    alias Pleroma.Activity    alias Pleroma.Object    alias Pleroma.Object.Fetcher -  alias Pleroma.Repo +  alias Pleroma.Tests.ObanHelpers    alias Pleroma.User    alias Pleroma.Web.ActivityPub.ActivityPub    alias Pleroma.Web.ActivityPub.Transmogrifier +  alias Pleroma.Web.AdminAPI.AccountView    alias Pleroma.Web.CommonAPI -  alias Pleroma.Web.OStatus -  alias Pleroma.Web.Websub.WebsubClientSubscription    import Mock    import Pleroma.Factory @@ -24,6 +23,8 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do      :ok    end +  clear_config([:instance, :max_remote_account_fields]) +    describe "handle_incoming" do      test "it ignores an incoming notice if we already have it" do        activity = insert(:note_activity) @@ -38,6 +39,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do        assert activity == returned_activity      end +    @tag capture_log: true      test "it fetches replied-to activities if we don't have them" do        data =          File.read!("test/fixtures/mastodon-post-activity.json") @@ -100,7 +102,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do        assert capture_log(fn ->                 {:ok, _returned_activity} = Transmogrifier.handle_incoming(data) -             end) =~ "[error] Couldn't fetch \"\"https://404.site/whatever\"\", error: nil" +             end) =~ "[error] Couldn't fetch \"https://404.site/whatever\", error: nil"      end      test "it works for incoming notices" do @@ -145,7 +147,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do        user = User.get_cached_by_ap_id(object_data["actor"]) -      assert user.info.note_count == 1 +      assert user.note_count == 1      end      test "it works for incoming notices with hashtags" do @@ -174,6 +176,35 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do               end)      end +    test "it works for incoming listens" do +      data = %{ +        "@context" => "https://www.w3.org/ns/activitystreams", +        "to" => ["https://www.w3.org/ns/activitystreams#Public"], +        "cc" => [], +        "type" => "Listen", +        "id" => "http://mastodon.example.org/users/admin/listens/1234/activity", +        "actor" => "http://mastodon.example.org/users/admin", +        "object" => %{ +          "type" => "Audio", +          "id" => "http://mastodon.example.org/users/admin/listens/1234", +          "attributedTo" => "http://mastodon.example.org/users/admin", +          "title" => "lain radio episode 1", +          "artist" => "lain", +          "album" => "lain radio", +          "length" => 180_000 +        } +      } + +      {:ok, %Activity{local: false} = activity} = Transmogrifier.handle_incoming(data) + +      object = Object.normalize(activity) + +      assert object.data["title"] == "lain radio episode 1" +      assert object.data["artist"] == "lain" +      assert object.data["album"] == "lain radio" +      assert object.data["length"] == 180_000 +    end +      test "it rewrites Note votes to Answers and increments vote counters on question activities" do        user = insert(:user) @@ -309,6 +340,80 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do        assert data["object"] == activity.data["object"]      end +    test "it works for incoming misskey likes, turning them into EmojiReactions" 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"] == "EmojiReaction" +      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 EmojiReactions" 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"] == "EmojiReaction" +      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"] == "EmojiReaction" +      assert data["id"] == "http://mastodon.example.org/users/admin#reactions/2" +      assert data["object"] == activity.data["object"] +      assert data["content"] == "👌" +    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"}) @@ -346,6 +451,31 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do        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!() @@ -385,6 +515,34 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do        assert Activity.get_create_by_object_ap_id(data["object"]).id == activity.id      end +    test "it works for incoming announces with an inlined activity" do +      data = +        File.read!("test/fixtures/mastodon-announce-private.json") +        |> Poison.decode!() + +      {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data) + +      assert data["actor"] == "http://mastodon.example.org/users/admin" +      assert data["type"] == "Announce" + +      assert data["id"] == +               "http://mastodon.example.org/users/admin/statuses/99542391527669785/activity" + +      object = Object.normalize(data["object"]) + +      assert object.data["id"] == "http://mastodon.example.org/@admin/99541947525187368" +      assert object.data["content"] == "this is a private toot" +    end + +    @tag capture_log: true +    test "it rejects incoming announces with an inlined activity from another origin" do +      data = +        File.read!("test/fixtures/bogus-mastodon-announce.json") +        |> Poison.decode!() + +      assert :error = Transmogrifier.handle_incoming(data) +    end +      test "it does not clobber the addressing on announce activities" do        user = insert(:user)        {:ok, activity} = CommonAPI.post(user, %{"status" => "hey"}) @@ -450,6 +608,41 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do        assert !is_nil(data["cc"])      end +    test "it strips internal likes" do +      data = +        File.read!("test/fixtures/mastodon-post-activity.json") +        |> Poison.decode!() + +      likes = %{ +        "first" => +          "http://mastodon.example.org/objects/dbdbc507-52c8-490d-9b7c-1e1d52e5c132/likes?page=1", +        "id" => "http://mastodon.example.org/objects/dbdbc507-52c8-490d-9b7c-1e1d52e5c132/likes", +        "totalItems" => 3, +        "type" => "OrderedCollection" +      } + +      object = Map.put(data["object"], "likes", likes) +      data = Map.put(data, "object", object) + +      {:ok, %Activity{object: object}} = Transmogrifier.handle_incoming(data) + +      refute Map.has_key?(object.data, "likes") +    end + +    test "it strips internal reactions" do +      user = insert(: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") +      assert Map.has_key?(object.data, "reaction_count") + +      object_data = Transmogrifier.strip_internal_fields(object.data) +      refute Map.has_key?(object_data, "reactions") +      refute Map.has_key?(object_data, "reaction_count") +    end +      test "it works for incoming update activities" do        data = File.read!("test/fixtures/mastodon-post-activity.json") |> Poison.decode!() @@ -468,6 +661,8 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do        {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(update_data) +      assert data["id"] == update_data["id"] +        user = User.get_cached_by_ap_id(data["actor"])        assert user.name == "gargle" @@ -478,7 +673,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do                 }               ] -      assert user.info.banner["url"] == [ +      assert user.banner["url"] == [                 %{                   "href" =>                     "https://cd.niu.moe/accounts/headers/000/033/323/original/850b3448fa5fd477.png" @@ -488,6 +683,99 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do        assert user.bio == "<p>Some bio</p>"      end +    test "it works with alsoKnownAs" do +      {:ok, %Activity{data: %{"actor" => actor}}} = +        "test/fixtures/mastodon-post-activity.json" +        |> File.read!() +        |> Poison.decode!() +        |> Transmogrifier.handle_incoming() + +      assert User.get_cached_by_ap_id(actor).also_known_as == ["http://example.org/users/foo"] + +      {:ok, _activity} = +        "test/fixtures/mastodon-update.json" +        |> File.read!() +        |> Poison.decode!() +        |> Map.put("actor", actor) +        |> Map.update!("object", fn object -> +          object +          |> Map.put("actor", actor) +          |> Map.put("id", actor) +          |> Map.put("alsoKnownAs", [ +            "http://mastodon.example.org/users/foo", +            "http://example.org/users/bar" +          ]) +        end) +        |> Transmogrifier.handle_incoming() + +      assert User.get_cached_by_ap_id(actor).also_known_as == [ +               "http://mastodon.example.org/users/foo", +               "http://example.org/users/bar" +             ] +    end + +    test "it works with custom profile fields" do +      {:ok, activity} = +        "test/fixtures/mastodon-post-activity.json" +        |> File.read!() +        |> Poison.decode!() +        |> Transmogrifier.handle_incoming() + +      user = User.get_cached_by_ap_id(activity.actor) + +      assert User.fields(user) == [ +               %{"name" => "foo", "value" => "bar"}, +               %{"name" => "foo1", "value" => "bar1"} +             ] + +      update_data = File.read!("test/fixtures/mastodon-update.json") |> Poison.decode!() + +      object = +        update_data["object"] +        |> Map.put("actor", user.ap_id) +        |> Map.put("id", user.ap_id) + +      update_data = +        update_data +        |> Map.put("actor", user.ap_id) +        |> Map.put("object", object) + +      {:ok, _update_activity} = Transmogrifier.handle_incoming(update_data) + +      user = User.get_cached_by_ap_id(user.ap_id) + +      assert User.fields(user) == [ +               %{"name" => "foo", "value" => "updated"}, +               %{"name" => "foo1", "value" => "updated"} +             ] + +      Pleroma.Config.put([:instance, :max_remote_account_fields], 2) + +      update_data = +        put_in(update_data, ["object", "attachment"], [ +          %{"name" => "foo", "type" => "PropertyValue", "value" => "bar"}, +          %{"name" => "foo11", "type" => "PropertyValue", "value" => "bar11"}, +          %{"name" => "foo22", "type" => "PropertyValue", "value" => "bar22"} +        ]) + +      {:ok, _} = Transmogrifier.handle_incoming(update_data) + +      user = User.get_cached_by_ap_id(user.ap_id) + +      assert User.fields(user) == [ +               %{"name" => "foo", "value" => "updated"}, +               %{"name" => "foo1", "value" => "updated"} +             ] + +      update_data = put_in(update_data, ["object", "attachment"], []) + +      {:ok, _} = Transmogrifier.handle_incoming(update_data) + +      user = User.get_cached_by_ap_id(user.ap_id) + +      assert User.fields(user) == [] +    end +      test "it works for incoming update activities which lock the account" do        data = File.read!("test/fixtures/mastodon-post-activity.json") |> Poison.decode!() @@ -508,11 +796,12 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do        {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(update_data)        user = User.get_cached_by_ap_id(data["actor"]) -      assert user.info.locked == true +      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") @@ -525,11 +814,14 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do        data =          data          |> Map.put("object", object) -        |> Map.put("actor", activity.data["actor"]) +        |> Map.put("actor", deleting_user.ap_id) -      {:ok, %Activity{local: false}} = Transmogrifier.handle_incoming(data) +      {: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 @@ -550,11 +842,12 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do        assert capture_log(fn ->                 :error = Transmogrifier.handle_incoming(data)               end) =~ -               "[error] Could not decode user at fetch http://mastodon.example.org/users/gargron, {:error, {:error, :nxdomain}}" +               "[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") @@ -563,6 +856,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do          |> Poison.decode!()        {:ok, _} = Transmogrifier.handle_incoming(data) +      ObanHelpers.perform_all()        refute User.get_cached_by_ap_id(ap_id)      end @@ -575,7 +869,10 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do          |> Poison.decode!()          |> Map.put("actor", ap_id) -      assert :error == Transmogrifier.handle_incoming(data) +      assert capture_log(fn -> +               assert :error == Transmogrifier.handle_incoming(data) +             end) =~ "Object containment failed" +        assert User.get_cached_by_ap_id(ap_id)      end @@ -633,6 +930,25 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do        refute User.following?(User.get_cached_by_ap_id(data["actor"]), user)      end +    test "it works for incoming follows to locked account" do +      pending_follower = insert(:user, ap_id: "http://mastodon.example.org/users/admin") +      user = insert(:user, locked: true) + +      data = +        File.read!("test/fixtures/mastodon-follow-activity.json") +        |> Poison.decode!() +        |> Map.put("object", user.ap_id) + +      {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data) + +      assert data["type"] == "Follow" +      assert data["object"] == user.ap_id +      assert data["state"] == "pending" +      assert data["actor"] == "http://mastodon.example.org/users/admin" + +      assert [^pending_follower] = User.get_follow_requests(user) +    end +      test "it works for incoming blocks" do        user = insert(:user) @@ -735,6 +1051,8 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do        assert activity.data["object"] == follow_activity.data["id"] +      assert activity.data["id"] == accept_data["id"] +        follower = User.get_cached_by_id(follower.id)        assert User.following?(follower, followed) == true @@ -742,7 +1060,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do      test "it works for incoming accepts which were orphaned" do        follower = insert(:user) -      followed = insert(:user, %{info: %User.Info{locked: true}}) +      followed = insert(:user, locked: true)        {:ok, follow_activity} = ActivityPub.follow(follower, followed) @@ -764,7 +1082,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do      test "it works for incoming accepts which are referenced by IRI only" do        follower = insert(:user) -      followed = insert(:user, %{info: %User.Info{locked: true}}) +      followed = insert(:user, locked: true)        {:ok, follow_activity} = ActivityPub.follow(follower, followed) @@ -784,7 +1102,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do      test "it fails for incoming accepts which cannot be correlated" do        follower = insert(:user) -      followed = insert(:user, %{info: %User.Info{locked: true}}) +      followed = insert(:user, locked: true)        accept_data =          File.read!("test/fixtures/mastodon-accept-activity.json") @@ -803,7 +1121,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do      test "it fails for incoming rejects which cannot be correlated" do        follower = insert(:user) -      followed = insert(:user, %{info: %User.Info{locked: true}}) +      followed = insert(:user, locked: true)        accept_data =          File.read!("test/fixtures/mastodon-reject-activity.json") @@ -822,7 +1140,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do      test "it works for incoming rejects which are orphaned" do        follower = insert(:user) -      followed = insert(:user, %{info: %User.Info{locked: true}}) +      followed = insert(:user, locked: true)        {:ok, follower} = User.follow(follower, followed)        {:ok, _follow_activity} = ActivityPub.follow(follower, followed) @@ -839,6 +1157,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do        {:ok, activity} = Transmogrifier.handle_incoming(reject_data)        refute activity.local +      assert activity.data["id"] == reject_data["id"]        follower = User.get_cached_by_id(follower.id) @@ -847,7 +1166,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do      test "it works for incoming rejects which are referenced by IRI only" do        follower = insert(:user) -      followed = insert(:user, %{info: %User.Info{locked: true}}) +      followed = insert(:user, locked: true)        {:ok, follower} = User.follow(follower, followed)        {:ok, follow_activity} = ActivityPub.follow(follower, followed) @@ -916,10 +1235,18 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do        {:ok, activity} = CommonAPI.post(user, %{"status" => "test post"})        object = Object.normalize(activity) +      note_obj = %{ +        "type" => "Note", +        "id" => activity.data["id"], +        "content" => "test post", +        "published" => object.data["published"], +        "actor" => AccountView.render("show.json", %{user: user}) +      } +        message = %{          "@context" => "https://www.w3.org/ns/activitystreams",          "cc" => [user.ap_id], -        "object" => [user.ap_id, object.data["id"]], +        "object" => [user.ap_id, activity.data["id"]],          "type" => "Flag",          "content" => "blocked AND reported!!!",          "actor" => other_user.ap_id @@ -927,14 +1254,95 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do        assert {:ok, activity} = Transmogrifier.handle_incoming(message) -      assert activity.data["object"] == [user.ap_id, object.data["id"]] +      assert activity.data["object"] == [user.ap_id, note_obj]        assert activity.data["content"] == "blocked AND reported!!!"        assert activity.data["actor"] == other_user.ap_id        assert activity.data["cc"] == [user.ap_id]      end + +    test "it correctly processes messages with non-array to field" do +      user = insert(:user) + +      message = %{ +        "@context" => "https://www.w3.org/ns/activitystreams", +        "to" => "https://www.w3.org/ns/activitystreams#Public", +        "type" => "Create", +        "object" => %{ +          "content" => "blah blah blah", +          "type" => "Note", +          "attributedTo" => user.ap_id, +          "inReplyTo" => nil +        }, +        "actor" => user.ap_id +      } + +      assert {:ok, activity} = Transmogrifier.handle_incoming(message) + +      assert ["https://www.w3.org/ns/activitystreams#Public"] == activity.data["to"] +    end + +    test "it correctly processes messages with non-array cc field" do +      user = insert(:user) + +      message = %{ +        "@context" => "https://www.w3.org/ns/activitystreams", +        "to" => user.follower_address, +        "cc" => "https://www.w3.org/ns/activitystreams#Public", +        "type" => "Create", +        "object" => %{ +          "content" => "blah blah blah", +          "type" => "Note", +          "attributedTo" => user.ap_id, +          "inReplyTo" => nil +        }, +        "actor" => user.ap_id +      } + +      assert {:ok, activity} = Transmogrifier.handle_incoming(message) + +      assert ["https://www.w3.org/ns/activitystreams#Public"] == activity.data["cc"] +      assert [user.follower_address] == activity.data["to"] +    end + +    test "it accepts Move activities" do +      old_user = insert(:user) +      new_user = insert(:user) + +      message = %{ +        "@context" => "https://www.w3.org/ns/activitystreams", +        "type" => "Move", +        "actor" => old_user.ap_id, +        "object" => old_user.ap_id, +        "target" => new_user.ap_id +      } + +      assert :error = Transmogrifier.handle_incoming(message) + +      {:ok, _new_user} = User.update_and_set_cache(new_user, %{also_known_as: [old_user.ap_id]}) + +      assert {:ok, %Activity{} = activity} = Transmogrifier.handle_incoming(message) +      assert activity.actor == old_user.ap_id +      assert activity.data["actor"] == old_user.ap_id +      assert activity.data["object"] == old_user.ap_id +      assert activity.data["target"] == new_user.ap_id +      assert activity.data["type"] == "Move" +    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, announce_activity, _} = CommonAPI.repeat(activity.id, user) + +      {:ok, modified} = Transmogrifier.prepare_outgoing(announce_activity.data) + +      assert modified["object"]["content"] == "hey" +      assert modified["object"]["actor"] == modified["object"]["attributedTo"] +    end +      test "it turns mentions into tags" do        user = insert(:user)        other_user = insert(:user) @@ -991,32 +1399,6 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do        assert modified["object"]["actor"] == modified["object"]["attributedTo"]      end -    test "it translates ostatus IDs to external URLs" do -      incoming = File.read!("test/fixtures/incoming_note_activity.xml") -      {:ok, [referent_activity]} = OStatus.handle_incoming(incoming) - -      user = insert(:user) - -      {:ok, activity, _} = CommonAPI.favorite(referent_activity.id, user) -      {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data) - -      assert modified["object"] == "http://gs.example.org:4040/index.php/notice/29" -    end - -    test "it translates ostatus reply_to IDs to external URLs" do -      incoming = File.read!("test/fixtures/incoming_note_activity.xml") -      {:ok, [referred_activity]} = OStatus.handle_incoming(incoming) - -      user = insert(:user) - -      {:ok, activity} = -        CommonAPI.post(user, %{"status" => "HI!", "in_reply_to_status_id" => referred_activity.id}) - -      {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data) - -      assert modified["object"]["inReplyTo"] == "http://gs.example.org:4040/index.php/notice/29" -    end -      test "it strips internal hashtag data" do        user = insert(:user) @@ -1061,14 +1443,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do        assert is_nil(modified["object"]["announcements"])        assert is_nil(modified["object"]["announcement_count"])        assert is_nil(modified["object"]["context_id"]) -    end - -    test "it adds like collection to object" do -      activity = insert(:note_activity) -      {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data) - -      assert modified["object"]["likes"]["type"] == "OrderedCollection" -      assert modified["object"]["likes"]["totalItems"] == 0 +      assert is_nil(modified["object"]["likes"])      end      test "the directMessage flag is present" do @@ -1110,6 +1485,20 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do        assert is_nil(modified["bcc"])      end + +    test "it can handle Listen activities" do +      listen_activity = insert(:listen) + +      {:ok, modified} = Transmogrifier.prepare_outgoing(listen_activity.data) + +      assert modified["type"] == "Listen" + +      user = insert(:user) + +      {:ok, activity} = CommonAPI.listen(user, %{"title" => "lain radio episode 1"}) + +      {:ok, _modified} = Transmogrifier.prepare_outgoing(activity.data) +    end    end    describe "user upgrade" do @@ -1122,23 +1511,26 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do            follower_address: User.ap_followers(%User{nickname: "rye@niu.moe"})          }) -      user_two = insert(:user, %{following: [user.follower_address]}) +      user_two = insert(:user) +      Pleroma.FollowingRelationship.follow(user_two, user, "accept")        {: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) -      assert user.info.note_count == 1 +      assert user.note_count == 1        {:ok, user} = Transmogrifier.upgrade_user_from_ap_id("https://niu.moe/users/rye") -      assert user.info.ap_enabled -      assert user.info.note_count == 1 +      ObanHelpers.perform_all() + +      assert user.ap_enabled +      assert user.note_count == 1        assert user.follower_address == "https://niu.moe/users/rye/followers"        assert user.following_address == "https://niu.moe/users/rye/following"        user = User.get_cached_by_id(user.id) -      assert user.info.note_count == 1 +      assert user.note_count == 1        activity = Activity.get_by_id(activity.id)        assert user.follower_address in activity.recipients @@ -1159,7 +1551,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do                       "https://cdn.niu.moe/accounts/headers/000/033/323/original/850b3448fa5fd477.png"                   }                 ] -             } = user.info.banner +             } = user.banner        refute "..." in activity.recipients @@ -1167,23 +1559,8 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do        refute user.follower_address in unrelated_activity.recipients        user_two = User.get_cached_by_id(user_two.id) -      assert user.follower_address in user_two.following -      refute "..." in user_two.following -    end -  end - -  describe "maybe_retire_websub" do -    test "it deletes all websub client subscripitions with the user as topic" do -      subscription = %WebsubClientSubscription{topic: "https://niu.moe/users/rye.atom"} -      {:ok, ws} = Repo.insert(subscription) - -      subscription = %WebsubClientSubscription{topic: "https://niu.moe/users/pasty.atom"} -      {:ok, ws2} = Repo.insert(subscription) - -      Transmogrifier.maybe_retire_websub("https://niu.moe/users/rye") - -      refute Repo.get(WebsubClientSubscription, ws.id) -      assert Repo.get(WebsubClientSubscription, ws2.id) +      assert User.following?(user_two, user) +      refute "..." in User.following(user_two)      end    end @@ -1209,7 +1586,9 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do          "type" => "Announce"        } -      :error = Transmogrifier.handle_incoming(data) +      assert capture_log(fn -> +               :error = Transmogrifier.handle_incoming(data) +             end) =~ "Object containment failed"      end      test "it rejects activities which reference objects that have an incorrect attribution (variant 1)" do @@ -1222,7 +1601,9 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do          "type" => "Announce"        } -      :error = Transmogrifier.handle_incoming(data) +      assert capture_log(fn -> +               :error = Transmogrifier.handle_incoming(data) +             end) =~ "Object containment failed"      end      test "it rejects activities which reference objects that have an incorrect attribution (variant 2)" do @@ -1235,7 +1616,9 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do          "type" => "Announce"        } -      :error = Transmogrifier.handle_incoming(data) +      assert capture_log(fn -> +               :error = Transmogrifier.handle_incoming(data) +             end) =~ "Object containment failed"      end    end @@ -1374,31 +1757,274 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do      end    end -  test "update_following_followers_counters/1" do -    user1 = -      insert(:user, -        local: false, -        follower_address: "http://localhost:4001/users/masto_closed/followers", -        following_address: "http://localhost:4001/users/masto_closed/following" -      ) +  describe "fix_summary/1" do +    test "returns fixed object" do +      assert Transmogrifier.fix_summary(%{"summary" => nil}) == %{"summary" => ""} +      assert Transmogrifier.fix_summary(%{"summary" => "ok"}) == %{"summary" => "ok"} +      assert Transmogrifier.fix_summary(%{}) == %{"summary" => ""} +    end +  end + +  describe "fix_in_reply_to/2" do +    clear_config([:instance, :federation_incoming_replies_max_depth]) + +    setup do +      data = Poison.decode!(File.read!("test/fixtures/mastodon-post-activity.json")) +      [data: data] +    end + +    test "returns not modified object when hasn't containts inReplyTo field", %{data: data} do +      assert Transmogrifier.fix_in_reply_to(data) == data +    end + +    test "returns object with inReplyToAtomUri when denied incoming reply", %{data: data} do +      Pleroma.Config.put([:instance, :federation_incoming_replies_max_depth], 0) + +      object_with_reply = +        Map.put(data["object"], "inReplyTo", "https://shitposter.club/notice/2827873") + +      modified_object = Transmogrifier.fix_in_reply_to(object_with_reply) +      assert modified_object["inReplyTo"] == "https://shitposter.club/notice/2827873" +      assert modified_object["inReplyToAtomUri"] == "https://shitposter.club/notice/2827873" + +      object_with_reply = +        Map.put(data["object"], "inReplyTo", %{"id" => "https://shitposter.club/notice/2827873"}) + +      modified_object = Transmogrifier.fix_in_reply_to(object_with_reply) +      assert modified_object["inReplyTo"] == %{"id" => "https://shitposter.club/notice/2827873"} +      assert modified_object["inReplyToAtomUri"] == "https://shitposter.club/notice/2827873" + +      object_with_reply = +        Map.put(data["object"], "inReplyTo", ["https://shitposter.club/notice/2827873"]) + +      modified_object = Transmogrifier.fix_in_reply_to(object_with_reply) +      assert modified_object["inReplyTo"] == ["https://shitposter.club/notice/2827873"] +      assert modified_object["inReplyToAtomUri"] == "https://shitposter.club/notice/2827873" + +      object_with_reply = Map.put(data["object"], "inReplyTo", []) +      modified_object = Transmogrifier.fix_in_reply_to(object_with_reply) +      assert modified_object["inReplyTo"] == [] +      assert modified_object["inReplyToAtomUri"] == "" +    end + +    @tag capture_log: true +    test "returns modified object when allowed incoming reply", %{data: data} do +      object_with_reply = +        Map.put( +          data["object"], +          "inReplyTo", +          "https://shitposter.club/notice/2827873" +        ) + +      Pleroma.Config.put([:instance, :federation_incoming_replies_max_depth], 5) +      modified_object = Transmogrifier.fix_in_reply_to(object_with_reply) + +      assert modified_object["inReplyTo"] == +               "tag:shitposter.club,2017-05-05:noticeId=2827873:objectType=comment" + +      assert modified_object["inReplyToAtomUri"] == "https://shitposter.club/notice/2827873" -    user2 = -      insert(:user, -        local: false, -        follower_address: "http://localhost:4001/users/fuser2/followers", -        following_address: "http://localhost:4001/users/fuser2/following" -      ) +      assert modified_object["conversation"] == +               "tag:shitposter.club,2017-05-05:objectType=thread:nonce=3c16e9c2681f6d26" -    Transmogrifier.update_following_followers_counters(user1) -    Transmogrifier.update_following_followers_counters(user2) +      assert modified_object["context"] == +               "tag:shitposter.club,2017-05-05:objectType=thread:nonce=3c16e9c2681f6d26" +    end +  end -    %{follower_count: followers, following_count: following} = User.get_cached_user_info(user1) -    assert followers == 437 -    assert following == 152 +  describe "fix_url/1" do +    test "fixes data for object when url is map" do +      object = %{ +        "url" => %{ +          "type" => "Link", +          "mimeType" => "video/mp4", +          "href" => "https://peede8d-46fb-ad81-2d4c2d1630e3-480.mp4" +        } +      } -    %{follower_count: followers, following_count: following} = User.get_cached_user_info(user2) +      assert Transmogrifier.fix_url(object) == %{ +               "url" => "https://peede8d-46fb-ad81-2d4c2d1630e3-480.mp4" +             } +    end -    assert followers == 527 -    assert following == 267 +    test "fixes data for video object" do +      object = %{ +        "type" => "Video", +        "url" => [ +          %{ +            "type" => "Link", +            "mimeType" => "video/mp4", +            "href" => "https://peede8d-46fb-ad81-2d4c2d1630e3-480.mp4" +          }, +          %{ +            "type" => "Link", +            "mimeType" => "video/mp4", +            "href" => "https://peertube46fb-ad81-2d4c2d1630e3-240.mp4" +          }, +          %{ +            "type" => "Link", +            "mimeType" => "text/html", +            "href" => "https://peertube.-2d4c2d1630e3" +          }, +          %{ +            "type" => "Link", +            "mimeType" => "text/html", +            "href" => "https://peertube.-2d4c2d16377-42" +          } +        ] +      } + +      assert Transmogrifier.fix_url(object) == %{ +               "attachment" => [ +                 %{ +                   "href" => "https://peede8d-46fb-ad81-2d4c2d1630e3-480.mp4", +                   "mimeType" => "video/mp4", +                   "type" => "Link" +                 } +               ], +               "type" => "Video", +               "url" => "https://peertube.-2d4c2d1630e3" +             } +    end + +    test "fixes url for not Video object" do +      object = %{ +        "type" => "Text", +        "url" => [ +          %{ +            "type" => "Link", +            "mimeType" => "text/html", +            "href" => "https://peertube.-2d4c2d1630e3" +          }, +          %{ +            "type" => "Link", +            "mimeType" => "text/html", +            "href" => "https://peertube.-2d4c2d16377-42" +          } +        ] +      } + +      assert Transmogrifier.fix_url(object) == %{ +               "type" => "Text", +               "url" => "https://peertube.-2d4c2d1630e3" +             } + +      assert Transmogrifier.fix_url(%{"type" => "Text", "url" => []}) == %{ +               "type" => "Text", +               "url" => "" +             } +    end + +    test "retunrs not modified object" do +      assert Transmogrifier.fix_url(%{"type" => "Text"}) == %{"type" => "Text"} +    end +  end + +  describe "get_obj_helper/2" do +    test "returns nil when cannot normalize object" do +      assert capture_log(fn -> +               refute Transmogrifier.get_obj_helper("test-obj-id") +             end) =~ "Unsupported URI scheme" +    end + +    @tag capture_log: true +    test "returns {:ok, %Object{}} for success case" do +      assert {:ok, %Object{}} = +               Transmogrifier.get_obj_helper("https://shitposter.club/notice/2827873") +    end +  end + +  describe "fix_attachments/1" do +    test "returns not modified object" do +      data = Poison.decode!(File.read!("test/fixtures/mastodon-post-activity.json")) +      assert Transmogrifier.fix_attachments(data) == data +    end + +    test "returns modified object when attachment is map" do +      assert Transmogrifier.fix_attachments(%{ +               "attachment" => %{ +                 "mediaType" => "video/mp4", +                 "url" => "https://peertube.moe/stat-480.mp4" +               } +             }) == %{ +               "attachment" => [ +                 %{ +                   "mediaType" => "video/mp4", +                   "url" => [ +                     %{ +                       "href" => "https://peertube.moe/stat-480.mp4", +                       "mediaType" => "video/mp4", +                       "type" => "Link" +                     } +                   ] +                 } +               ] +             } +    end + +    test "returns modified object when attachment is list" do +      assert Transmogrifier.fix_attachments(%{ +               "attachment" => [ +                 %{"mediaType" => "video/mp4", "url" => "https://pe.er/stat-480.mp4"}, +                 %{"mimeType" => "video/mp4", "href" => "https://pe.er/stat-480.mp4"} +               ] +             }) == %{ +               "attachment" => [ +                 %{ +                   "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", +                   "mimeType" => "video/mp4", +                   "url" => [ +                     %{ +                       "href" => "https://pe.er/stat-480.mp4", +                       "mediaType" => "video/mp4", +                       "type" => "Link" +                     } +                   ] +                 } +               ] +             } +    end +  end + +  describe "fix_emoji/1" do +    test "returns not modified object when object not contains tags" do +      data = Poison.decode!(File.read!("test/fixtures/mastodon-post-activity.json")) +      assert Transmogrifier.fix_emoji(data) == data +    end + +    test "returns object with emoji when object contains list tags" do +      assert Transmogrifier.fix_emoji(%{ +               "tag" => [ +                 %{"type" => "Emoji", "name" => ":bib:", "icon" => %{"url" => "/test"}}, +                 %{"type" => "Hashtag"} +               ] +             }) == %{ +               "emoji" => %{"bib" => "/test"}, +               "tag" => [ +                 %{"icon" => %{"url" => "/test"}, "name" => ":bib:", "type" => "Emoji"}, +                 %{"type" => "Hashtag"} +               ] +             } +    end + +    test "returns object with emoji when object contains map tag" do +      assert Transmogrifier.fix_emoji(%{ +               "tag" => %{"type" => "Emoji", "name" => ":bib:", "icon" => %{"url" => "/test"}} +             }) == %{ +               "emoji" => %{"bib" => "/test"}, +               "tag" => %{"icon" => %{"url" => "/test"}, "name" => ":bib:", "type" => "Emoji"} +             } +    end    end  end diff --git a/test/web/activity_pub/utils_test.exs b/test/web/activity_pub/utils_test.exs index ca5f057a7..211fa6c95 100644 --- a/test/web/activity_pub/utils_test.exs +++ b/test/web/activity_pub/utils_test.exs @@ -10,10 +10,13 @@ defmodule Pleroma.Web.ActivityPub.UtilsTest do    alias Pleroma.User    alias Pleroma.Web.ActivityPub.ActivityPub    alias Pleroma.Web.ActivityPub.Utils +  alias Pleroma.Web.AdminAPI.AccountView    alias Pleroma.Web.CommonAPI    import Pleroma.Factory +  require Pleroma.Constants +    describe "fetch the latest Follow" do      test "fetches the latest Follow activity" do        %Activity{data: %{"type" => "Follow"}} = activity = insert(:follow_activity) @@ -85,6 +88,46 @@ defmodule Pleroma.Web.ActivityPub.UtilsTest do        assert Utils.determine_explicit_mentions(object) == []      end + +    test "works with an object has tags as map" do +      object = %{ +        "tag" => %{ +          "type" => "Mention", +          "href" => "https://example.com/~alyssa", +          "name" => "Alyssa P. Hacker" +        } +      } + +      assert Utils.determine_explicit_mentions(object) == ["https://example.com/~alyssa"] +    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 @@ -255,7 +298,7 @@ defmodule Pleroma.Web.ActivityPub.UtilsTest do    describe "update_follow_state_for_all/2" do      test "updates the state of all Follow activities with the same actor and object" do -      user = insert(:user, info: %{locked: true}) +      user = insert(:user, locked: true)        follower = insert(:user)        {:ok, follow_activity} = ActivityPub.follow(follower, user) @@ -272,14 +315,14 @@ defmodule Pleroma.Web.ActivityPub.UtilsTest do        {:ok, follow_activity_two} =          Utils.update_follow_state_for_all(follow_activity_two, "accept") -      assert Repo.get(Activity, follow_activity.id).data["state"] == "accept" -      assert Repo.get(Activity, follow_activity_two.id).data["state"] == "accept" +      assert refresh_record(follow_activity).data["state"] == "accept" +      assert refresh_record(follow_activity_two).data["state"] == "accept"      end    end    describe "update_follow_state/2" do      test "updates the state of the given follow activity" do -      user = insert(:user, info: %{locked: true}) +      user = insert(:user, locked: true)        follower = insert(:user)        {:ok, follow_activity} = ActivityPub.follow(follower, user) @@ -295,8 +338,315 @@ defmodule Pleroma.Web.ActivityPub.UtilsTest do        {:ok, follow_activity_two} = Utils.update_follow_state(follow_activity_two, "reject") -      assert Repo.get(Activity, follow_activity.id).data["state"] == "pending" -      assert Repo.get(Activity, follow_activity_two.id).data["state"] == "reject" +      assert refresh_record(follow_activity).data["state"] == "pending" +      assert refresh_record(follow_activity_two).data["state"] == "reject" +    end +  end + +  describe "update_element_in_object/3" do +    test "updates likes" do +      user = insert(:user) +      activity = insert(:note_activity) +      object = Object.normalize(activity) + +      assert {:ok, updated_object} = +               Utils.update_element_in_object( +                 "like", +                 [user.ap_id], +                 object +               ) + +      assert updated_object.data["likes"] == [user.ap_id] +      assert updated_object.data["like_count"] == 1 +    end +  end + +  describe "add_like_to_object/2" do +    test "add actor to likes" do +      user = insert(:user) +      user2 = insert(:user) +      object = insert(:note) + +      assert {:ok, updated_object} = +               Utils.add_like_to_object( +                 %Activity{data: %{"actor" => user.ap_id}}, +                 object +               ) + +      assert updated_object.data["likes"] == [user.ap_id] +      assert updated_object.data["like_count"] == 1 + +      assert {:ok, updated_object2} = +               Utils.add_like_to_object( +                 %Activity{data: %{"actor" => user2.ap_id}}, +                 updated_object +               ) + +      assert updated_object2.data["likes"] == [user2.ap_id, user.ap_id] +      assert updated_object2.data["like_count"] == 2 +    end +  end + +  describe "remove_like_from_object/2" do +    test "removes ap_id from likes" do +      user = insert(:user) +      user2 = insert(:user) +      object = insert(:note, data: %{"likes" => [user.ap_id, user2.ap_id], "like_count" => 2}) + +      assert {:ok, updated_object} = +               Utils.remove_like_from_object( +                 %Activity{data: %{"actor" => user.ap_id}}, +                 object +               ) + +      assert updated_object.data["likes"] == [user2.ap_id] +      assert updated_object.data["like_count"] == 1 +    end +  end + +  describe "get_existing_like/2" do +    test "fetches existing like" do +      note_activity = insert(:note_activity) +      assert object = Object.normalize(note_activity) + +      user = insert(:user) +      refute Utils.get_existing_like(user.ap_id, object) +      {:ok, like_activity, _object} = ActivityPub.like(user, object) + +      assert ^like_activity = Utils.get_existing_like(user.ap_id, object) +    end +  end + +  describe "get_get_existing_announce/2" do +    test "returns nil if announce not found" do +      actor = insert(:user) +      refute Utils.get_existing_announce(actor.ap_id, %{data: %{"id" => "test"}}) +    end + +    test "fetches existing announce" do +      note_activity = insert(:note_activity) +      assert object = Object.normalize(note_activity) +      actor = insert(:user) + +      {:ok, announce, _object} = ActivityPub.announce(actor, object) +      assert Utils.get_existing_announce(actor.ap_id, object) == announce +    end +  end + +  describe "fetch_latest_block/2" do +    test "fetches last block activities" do +      user1 = insert(:user) +      user2 = insert(:user) + +      assert {:ok, %Activity{} = _} = ActivityPub.block(user1, user2) +      assert {:ok, %Activity{} = _} = ActivityPub.block(user1, user2) +      assert {:ok, %Activity{} = activity} = ActivityPub.block(user1, user2) + +      assert Utils.fetch_latest_block(user1, user2) == activity +    end +  end + +  describe "recipient_in_message/3" do +    test "returns true when recipient in `to`" do +      recipient = insert(:user) +      actor = insert(:user) +      assert Utils.recipient_in_message(recipient, actor, %{"to" => recipient.ap_id}) + +      assert Utils.recipient_in_message( +               recipient, +               actor, +               %{"to" => [recipient.ap_id], "cc" => ""} +             ) +    end + +    test "returns true when recipient in `cc`" do +      recipient = insert(:user) +      actor = insert(:user) +      assert Utils.recipient_in_message(recipient, actor, %{"cc" => recipient.ap_id}) + +      assert Utils.recipient_in_message( +               recipient, +               actor, +               %{"cc" => [recipient.ap_id], "to" => ""} +             ) +    end + +    test "returns true when recipient in `bto`" do +      recipient = insert(:user) +      actor = insert(:user) +      assert Utils.recipient_in_message(recipient, actor, %{"bto" => recipient.ap_id}) + +      assert Utils.recipient_in_message( +               recipient, +               actor, +               %{"bcc" => "", "bto" => [recipient.ap_id]} +             ) +    end + +    test "returns true when recipient in `bcc`" do +      recipient = insert(:user) +      actor = insert(:user) +      assert Utils.recipient_in_message(recipient, actor, %{"bcc" => recipient.ap_id}) + +      assert Utils.recipient_in_message( +               recipient, +               actor, +               %{"bto" => "", "bcc" => [recipient.ap_id]} +             ) +    end + +    test "returns true when message without addresses fields" do +      recipient = insert(:user) +      actor = insert(:user) +      assert Utils.recipient_in_message(recipient, actor, %{"bccc" => recipient.ap_id}) + +      assert Utils.recipient_in_message( +               recipient, +               actor, +               %{"btod" => "", "bccc" => [recipient.ap_id]} +             ) +    end + +    test "returns false" do +      recipient = insert(:user) +      actor = insert(:user) +      refute Utils.recipient_in_message(recipient, actor, %{"to" => "ap_id"}) +    end +  end + +  describe "lazy_put_activity_defaults/2" do +    test "returns map with id and published data" do +      note_activity = insert(:note_activity) +      object = Object.normalize(note_activity) +      res = Utils.lazy_put_activity_defaults(%{"context" => object.data["id"]}) +      assert res["context"] == object.data["id"] +      assert res["context_id"] == object.id +      assert res["id"] +      assert res["published"] +    end + +    test "returns map with fake id and published data" do +      assert %{ +               "context" => "pleroma:fakecontext", +               "context_id" => -1, +               "id" => "pleroma:fakeid", +               "published" => _ +             } = Utils.lazy_put_activity_defaults(%{}, true) +    end + +    test "returns activity data with object" do +      note_activity = insert(:note_activity) +      object = Object.normalize(note_activity) + +      res = +        Utils.lazy_put_activity_defaults(%{ +          "context" => object.data["id"], +          "object" => %{} +        }) + +      assert res["context"] == object.data["id"] +      assert res["context_id"] == object.id +      assert res["id"] +      assert res["published"] +      assert res["object"]["id"] +      assert res["object"]["published"] +      assert res["object"]["context"] == object.data["id"] +      assert res["object"]["context_id"] == object.id +    end +  end + +  describe "make_flag_data" do +    test "returns empty map when params is invalid" do +      assert Utils.make_flag_data(%{}, %{}) == %{} +    end + +    test "returns map with Flag object" do +      reporter = insert(:user) +      target_account = insert(:user) +      {:ok, activity} = CommonAPI.post(target_account, %{"status" => "foobar"}) +      context = Utils.generate_context_id() +      content = "foobar" + +      target_ap_id = target_account.ap_id +      activity_ap_id = activity.data["id"] + +      res = +        Utils.make_flag_data( +          %{ +            actor: reporter, +            context: context, +            account: target_account, +            statuses: [%{"id" => activity.data["id"]}], +            content: content +          }, +          %{} +        ) + +      note_obj = %{ +        "type" => "Note", +        "id" => activity_ap_id, +        "content" => content, +        "published" => activity.object.data["published"], +        "actor" => AccountView.render("show.json", %{user: target_account}) +      } + +      assert %{ +               "type" => "Flag", +               "content" => ^content, +               "context" => ^context, +               "object" => [^target_ap_id, ^note_obj], +               "state" => "open" +             } = res +    end +  end + +  describe "add_announce_to_object/2" do +    test "adds actor to announcement" do +      user = insert(:user) +      object = insert(:note) + +      activity = +        insert(:note_activity, +          data: %{ +            "actor" => user.ap_id, +            "cc" => [Pleroma.Constants.as_public()] +          } +        ) + +      assert {:ok, updated_object} = Utils.add_announce_to_object(activity, object) +      assert updated_object.data["announcements"] == [user.ap_id] +      assert updated_object.data["announcement_count"] == 1 +    end +  end + +  describe "remove_announce_from_object/2" do +    test "removes actor from announcements" do +      user = insert(:user) +      user2 = insert(:user) + +      object = +        insert(:note, +          data: %{"announcements" => [user.ap_id, user2.ap_id], "announcement_count" => 2} +        ) + +      activity = insert(:note_activity, data: %{"actor" => user.ap_id}) + +      assert {:ok, updated_object} = Utils.remove_announce_from_object(activity, object) +      assert updated_object.data["announcements"] == [user2.ap_id] +      assert updated_object.data["announcement_count"] == 1 +    end +  end + +  describe "get_cached_emoji_reactions/1" do +    test "returns the data or an emtpy list" do +      object = insert(:note) +      assert Utils.get_cached_emoji_reactions(object) == [] + +      object = insert(:note, data: %{"reactions" => [["x", ["lain"]]]}) +      assert Utils.get_cached_emoji_reactions(object) == [["x", ["lain"]]] + +      object = insert(:note, data: %{"reactions" => %{}}) +      assert Utils.get_cached_emoji_reactions(object) == []      end    end  end diff --git a/test/web/activity_pub/views/user_view_test.exs b/test/web/activity_pub/views/user_view_test.exs index 86254117f..8374b8d23 100644 --- a/test/web/activity_pub/views/user_view_test.exs +++ b/test/web/activity_pub/views/user_view_test.exs @@ -22,6 +22,37 @@ defmodule Pleroma.Web.ActivityPub.UserViewTest do      assert String.contains?(result["publicKey"]["publicKeyPem"], "BEGIN PUBLIC KEY")    end +  test "Renders profile fields" do +    fields = [ +      %{"name" => "foo", "value" => "bar"} +    ] + +    {:ok, user} = +      insert(:user) +      |> User.upgrade_changeset(%{fields: fields}) +      |> User.update_and_set_cache() + +    assert %{ +             "attachment" => [%{"name" => "foo", "type" => "PropertyValue", "value" => "bar"}] +           } = UserView.render("user.json", %{user: user}) +  end + +  test "Renders with emoji tags" do +    user = insert(:user, emoji: [%{"bib" => "/test"}]) + +    assert %{ +             "tag" => [ +               %{ +                 "icon" => %{"type" => "Image", "url" => "/test"}, +                 "id" => "/test", +                 "name" => ":bib:", +                 "type" => "Emoji", +                 "updated" => "1970-01-01T00:00:00Z" +               } +             ] +           } = UserView.render("user.json", %{user: user}) +  end +    test "Does not add an avatar image if the user hasn't set one" do      user = insert(:user)      {:ok, user} = User.ensure_keys_present(user) @@ -33,9 +64,7 @@ defmodule Pleroma.Web.ActivityPub.UserViewTest do      user =        insert(:user,          avatar: %{"url" => [%{"href" => "https://someurl"}]}, -        info: %{ -          banner: %{"url" => [%{"href" => "https://somebanner"}]} -        } +        banner: %{"url" => [%{"href" => "https://somebanner"}]}        )      {:ok, user} = User.ensure_keys_present(user) @@ -45,6 +74,12 @@ defmodule Pleroma.Web.ActivityPub.UserViewTest do      assert result["image"]["url"] == "https://somebanner"    end +  test "renders an invisible user with the invisible property set to true" do +    user = insert(:user, invisible: true) + +    assert %{"invisible" => true} = UserView.render("service.json", %{user: user}) +  end +    describe "endpoints" do      test "local users have a usable endpoints structure" do        user = insert(:user) @@ -90,9 +125,17 @@ defmodule Pleroma.Web.ActivityPub.UserViewTest do        other_user = insert(:user)        {:ok, _other_user, user, _activity} = CommonAPI.follow(other_user, user)        assert %{"totalItems" => 1} = UserView.render("followers.json", %{user: user}) -      info = Map.put(user.info, :hide_followers, true) -      user = Map.put(user, :info, info) -      assert %{"totalItems" => 0} = UserView.render("followers.json", %{user: user}) +      user = Map.merge(user, %{hide_followers_count: true, hide_followers: true}) +      refute UserView.render("followers.json", %{user: user}) |> Map.has_key?("totalItems") +    end + +    test "sets correct totalItems when followers are hidden but the follower counter is not" do +      user = insert(:user) +      other_user = insert(:user) +      {:ok, _other_user, user, _activity} = CommonAPI.follow(other_user, user) +      assert %{"totalItems" => 1} = UserView.render("followers.json", %{user: user}) +      user = Map.merge(user, %{hide_followers_count: false, hide_followers: true}) +      assert %{"totalItems" => 1} = UserView.render("followers.json", %{user: user})      end    end @@ -102,9 +145,48 @@ defmodule Pleroma.Web.ActivityPub.UserViewTest do        other_user = insert(:user)        {:ok, user, _other_user, _activity} = CommonAPI.follow(user, other_user)        assert %{"totalItems" => 1} = UserView.render("following.json", %{user: user}) -      info = Map.put(user.info, :hide_follows, true) -      user = Map.put(user, :info, info) +      user = Map.merge(user, %{hide_follows_count: true, hide_follows: true})        assert %{"totalItems" => 0} = UserView.render("following.json", %{user: user})      end + +    test "sets correct totalItems when follows are hidden but the follow counter is not" do +      user = insert(:user) +      other_user = insert(:user) +      {:ok, user, _other_user, _activity} = CommonAPI.follow(user, other_user) +      assert %{"totalItems" => 1} = UserView.render("following.json", %{user: user}) +      user = Map.merge(user, %{hide_follows_count: false, hide_follows: true}) +      assert %{"totalItems" => 1} = UserView.render("following.json", %{user: user}) +    end +  end + +  test "activity collection page aginates correctly" do +    user = insert(:user) + +    posts = +      for i <- 0..25 do +        {:ok, activity} = CommonAPI.post(user, %{"status" => "post #{i}"}) +        activity +      end + +    # outbox sorts chronologically, newest first, with ten per page +    posts = Enum.reverse(posts) + +    %{"next" => next_url} = +      UserView.render("activity_collection_page.json", %{ +        iri: "#{user.ap_id}/outbox", +        activities: Enum.take(posts, 10) +      }) + +    next_id = Enum.at(posts, 9).id +    assert next_url =~ next_id + +    %{"next" => next_url} = +      UserView.render("activity_collection_page.json", %{ +        iri: "#{user.ap_id}/outbox", +        activities: Enum.take(Enum.drop(posts, 10), 10) +      }) + +    next_id = Enum.at(posts, 19).id +    assert next_url =~ next_id    end  end diff --git a/test/web/activity_pub/visibilty_test.exs b/test/web/activity_pub/visibilty_test.exs index b62a89e68..4c2e0d207 100644 --- a/test/web/activity_pub/visibilty_test.exs +++ b/test/web/activity_pub/visibilty_test.exs @@ -212,7 +212,8 @@ defmodule Pleroma.Web.ActivityPub.VisibilityTest do      test "returns true if user following to author" do        author = insert(:user) -      user = insert(:user, following: [author.ap_id]) +      user = insert(:user) +      Pleroma.User.follow(user, author)        activity =          insert(:note_activity, diff --git a/test/web/admin_api/admin_api_controller_test.exs b/test/web/admin_api/admin_api_controller_test.exs index ee48b752c..5c767219a 100644 --- a/test/web/admin_api/admin_api_controller_test.exs +++ b/test/web/admin_api/admin_api_controller_test.exs @@ -1,58 +1,324 @@  # Pleroma: A lightweight social networking server -# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do    use Pleroma.Web.ConnCase +  use Oban.Testing, repo: Pleroma.Repo    alias Pleroma.Activity +  alias Pleroma.ConfigDB    alias Pleroma.HTML +  alias Pleroma.ModerationLog +  alias Pleroma.Repo +  alias Pleroma.ReportNote +  alias Pleroma.Tests.ObanHelpers    alias Pleroma.User    alias Pleroma.UserInviteToken +  alias Pleroma.Web.ActivityPub.Relay    alias Pleroma.Web.CommonAPI +  alias Pleroma.Web.MastodonAPI.StatusView    alias Pleroma.Web.MediaProxy    import Pleroma.Factory -  describe "/api/pleroma/admin/users" do -    test "Delete" do -      admin = insert(:user, info: %{is_admin: true}) +  setup_all do +    Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end) + +    :ok +  end + +  setup do +    admin = insert(:user, is_admin: true) +    token = insert(:oauth_admin_token, user: admin) + +    conn = +      build_conn() +      |> assign(:user, admin) +      |> assign(:token, token) + +    {:ok, %{admin: admin, token: token, conn: conn}} +  end + +  describe "with [:auth, :enforce_oauth_admin_scope_usage]," do +    clear_config([:auth, :enforce_oauth_admin_scope_usage]) do +      Pleroma.Config.put([:auth, :enforce_oauth_admin_scope_usage], true) +    end + +    test "GET /api/pleroma/admin/users/:nickname requires admin:read:accounts or broader scope", +         %{admin: admin} do +      user = insert(:user) +      url = "/api/pleroma/admin/users/#{user.nickname}" + +      good_token1 = insert(:oauth_token, user: admin, scopes: ["admin"]) +      good_token2 = insert(:oauth_token, user: admin, scopes: ["admin:read"]) +      good_token3 = insert(:oauth_token, user: admin, scopes: ["admin:read:accounts"]) + +      bad_token1 = insert(:oauth_token, user: admin, scopes: ["read:accounts"]) +      bad_token2 = insert(:oauth_token, user: admin, scopes: ["admin:read:accounts:partial"]) +      bad_token3 = nil + +      for good_token <- [good_token1, good_token2, good_token3] do +        conn = +          build_conn() +          |> assign(:user, admin) +          |> assign(:token, good_token) +          |> get(url) + +        assert json_response(conn, 200) +      end + +      for good_token <- [good_token1, good_token2, good_token3] do +        conn = +          build_conn() +          |> assign(:user, nil) +          |> assign(:token, good_token) +          |> get(url) + +        assert json_response(conn, :forbidden) +      end + +      for bad_token <- [bad_token1, bad_token2, bad_token3] do +        conn = +          build_conn() +          |> assign(:user, admin) +          |> assign(:token, bad_token) +          |> get(url) + +        assert json_response(conn, :forbidden) +      end +    end +  end + +  describe "unless [:auth, :enforce_oauth_admin_scope_usage]," do +    clear_config([:auth, :enforce_oauth_admin_scope_usage]) do +      Pleroma.Config.put([:auth, :enforce_oauth_admin_scope_usage], false) +    end + +    test "GET /api/pleroma/admin/users/:nickname requires " <> +           "read:accounts or admin:read:accounts or broader scope", +         %{admin: admin} do +      user = insert(:user) +      url = "/api/pleroma/admin/users/#{user.nickname}" + +      good_token1 = insert(:oauth_token, user: admin, scopes: ["admin"]) +      good_token2 = insert(:oauth_token, user: admin, scopes: ["admin:read"]) +      good_token3 = insert(:oauth_token, user: admin, scopes: ["admin:read:accounts"]) +      good_token4 = insert(:oauth_token, user: admin, scopes: ["read:accounts"]) +      good_token5 = insert(:oauth_token, user: admin, scopes: ["read"]) + +      good_tokens = [good_token1, good_token2, good_token3, good_token4, good_token5] + +      bad_token1 = insert(:oauth_token, user: admin, scopes: ["read:accounts:partial"]) +      bad_token2 = insert(:oauth_token, user: admin, scopes: ["admin:read:accounts:partial"]) +      bad_token3 = nil + +      for good_token <- good_tokens do +        conn = +          build_conn() +          |> assign(:user, admin) +          |> assign(:token, good_token) +          |> get(url) + +        assert json_response(conn, 200) +      end + +      for good_token <- good_tokens do +        conn = +          build_conn() +          |> assign(:user, nil) +          |> assign(:token, good_token) +          |> get(url) + +        assert json_response(conn, :forbidden) +      end + +      for bad_token <- [bad_token1, bad_token2, bad_token3] do +        conn = +          build_conn() +          |> assign(:user, admin) +          |> assign(:token, bad_token) +          |> get(url) + +        assert json_response(conn, :forbidden) +      end +    end +  end + +  describe "DELETE /api/pleroma/admin/users" do +    test "single user", %{admin: admin, conn: conn} do        user = insert(:user)        conn = -        build_conn() -        |> assign(:user, admin) +        conn          |> put_req_header("accept", "application/json")          |> delete("/api/pleroma/admin/users?nickname=#{user.nickname}") +      log_entry = Repo.one(ModerationLog) + +      assert ModerationLog.get_log_entry_message(log_entry) == +               "@#{admin.nickname} deleted users: @#{user.nickname}" +        assert json_response(conn, 200) == user.nickname      end -    test "Create" do -      admin = insert(:user, info: %{is_admin: true}) +    test "multiple users", %{admin: admin, conn: conn} do +      user_one = insert(:user) +      user_two = insert(:user)        conn = -        build_conn() -        |> assign(:user, admin) +        conn +        |> put_req_header("accept", "application/json") +        |> delete("/api/pleroma/admin/users", %{ +          nicknames: [user_one.nickname, user_two.nickname] +        }) + +      log_entry = Repo.one(ModerationLog) + +      assert ModerationLog.get_log_entry_message(log_entry) == +               "@#{admin.nickname} deleted users: @#{user_one.nickname}, @#{user_two.nickname}" + +      response = json_response(conn, 200) +      assert response -- [user_one.nickname, user_two.nickname] == [] +    end +  end + +  describe "/api/pleroma/admin/users" do +    test "Create", %{conn: conn} do +      conn = +        conn +        |> put_req_header("accept", "application/json") +        |> post("/api/pleroma/admin/users", %{ +          "users" => [ +            %{ +              "nickname" => "lain", +              "email" => "lain@example.org", +              "password" => "test" +            }, +            %{ +              "nickname" => "lain2", +              "email" => "lain2@example.org", +              "password" => "test" +            } +          ] +        }) + +      response = json_response(conn, 200) |> Enum.map(&Map.get(&1, "type")) +      assert response == ["success", "success"] + +      log_entry = Repo.one(ModerationLog) + +      assert ["lain", "lain2"] -- Enum.map(log_entry.data["subjects"], & &1["nickname"]) == [] +    end + +    test "Cannot create user with existing email", %{conn: conn} do +      user = insert(:user) + +      conn = +        conn +        |> put_req_header("accept", "application/json") +        |> post("/api/pleroma/admin/users", %{ +          "users" => [ +            %{ +              "nickname" => "lain", +              "email" => user.email, +              "password" => "test" +            } +          ] +        }) + +      assert json_response(conn, 409) == [ +               %{ +                 "code" => 409, +                 "data" => %{ +                   "email" => user.email, +                   "nickname" => "lain" +                 }, +                 "error" => "email has already been taken", +                 "type" => "error" +               } +             ] +    end + +    test "Cannot create user with existing nickname", %{conn: conn} do +      user = insert(:user) + +      conn = +        conn          |> put_req_header("accept", "application/json")          |> post("/api/pleroma/admin/users", %{ -          "nickname" => "lain", -          "email" => "lain@example.org", -          "password" => "test" +          "users" => [ +            %{ +              "nickname" => user.nickname, +              "email" => "someuser@plerama.social", +              "password" => "test" +            } +          ]          }) -      assert json_response(conn, 200) == "lain" +      assert json_response(conn, 409) == [ +               %{ +                 "code" => 409, +                 "data" => %{ +                   "email" => "someuser@plerama.social", +                   "nickname" => user.nickname +                 }, +                 "error" => "nickname has already been taken", +                 "type" => "error" +               } +             ] +    end + +    test "Multiple user creation works in transaction", %{conn: conn} do +      user = insert(:user) + +      conn = +        conn +        |> put_req_header("accept", "application/json") +        |> post("/api/pleroma/admin/users", %{ +          "users" => [ +            %{ +              "nickname" => "newuser", +              "email" => "newuser@pleroma.social", +              "password" => "test" +            }, +            %{ +              "nickname" => "lain", +              "email" => user.email, +              "password" => "test" +            } +          ] +        }) + +      assert json_response(conn, 409) == [ +               %{ +                 "code" => 409, +                 "data" => %{ +                   "email" => user.email, +                   "nickname" => "lain" +                 }, +                 "error" => "email has already been taken", +                 "type" => "error" +               }, +               %{ +                 "code" => 409, +                 "data" => %{ +                   "email" => "newuser@pleroma.social", +                   "nickname" => "newuser" +                 }, +                 "error" => "", +                 "type" => "error" +               } +             ] + +      assert User.get_by_nickname("newuser") === nil      end    end    describe "/api/pleroma/admin/users/:nickname" do      test "Show", %{conn: conn} do -      admin = insert(:user, info: %{is_admin: true})        user = insert(:user) -      conn = -        conn -        |> assign(:user, admin) -        |> get("/api/pleroma/admin/users/#{user.nickname}") +      conn = get(conn, "/api/pleroma/admin/users/#{user.nickname}")        expected = %{          "deactivated" => false, @@ -62,33 +328,28 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do          "roles" => %{"admin" => false, "moderator" => false},          "tags" => [],          "avatar" => User.avatar_url(user) |> MediaProxy.url(), -        "display_name" => HTML.strip_tags(user.name || user.nickname) +        "display_name" => HTML.strip_tags(user.name || user.nickname), +        "confirmation_pending" => false        }        assert expected == json_response(conn, 200)      end      test "when the user doesn't exist", %{conn: conn} do -      admin = insert(:user, info: %{is_admin: true})        user = build(:user) -      conn = -        conn -        |> assign(:user, admin) -        |> get("/api/pleroma/admin/users/#{user.nickname}") +      conn = get(conn, "/api/pleroma/admin/users/#{user.nickname}")        assert "Not found" == json_response(conn, 404)      end    end    describe "/api/pleroma/admin/users/follow" do -    test "allows to force-follow another user" do -      admin = insert(:user, info: %{is_admin: true}) +    test "allows to force-follow another user", %{admin: admin, conn: conn} do        user = insert(:user)        follower = insert(:user) -      build_conn() -      |> assign(:user, admin) +      conn        |> put_req_header("accept", "application/json")        |> post("/api/pleroma/admin/users/follow", %{          "follower" => follower.nickname, @@ -99,19 +360,22 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do        follower = User.get_cached_by_id(follower.id)        assert User.following?(follower, user) + +      log_entry = Repo.one(ModerationLog) + +      assert ModerationLog.get_log_entry_message(log_entry) == +               "@#{admin.nickname} made @#{follower.nickname} follow @#{user.nickname}"      end    end    describe "/api/pleroma/admin/users/unfollow" do -    test "allows to force-unfollow another user" do -      admin = insert(:user, info: %{is_admin: true}) +    test "allows to force-unfollow another user", %{admin: admin, conn: conn} do        user = insert(:user)        follower = insert(:user)        User.follow(follower, user) -      build_conn() -      |> assign(:user, admin) +      conn        |> put_req_header("accept", "application/json")        |> post("/api/pleroma/admin/users/unfollow", %{          "follower" => follower.nickname, @@ -122,24 +386,26 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do        follower = User.get_cached_by_id(follower.id)        refute User.following?(follower, user) + +      log_entry = Repo.one(ModerationLog) + +      assert ModerationLog.get_log_entry_message(log_entry) == +               "@#{admin.nickname} made @#{follower.nickname} unfollow @#{user.nickname}"      end    end    describe "PUT /api/pleroma/admin/users/tag" do -    setup do -      admin = insert(:user, info: %{is_admin: true}) +    setup %{conn: conn} do        user1 = insert(:user, %{tags: ["x"]})        user2 = insert(:user, %{tags: ["y"]})        user3 = insert(:user, %{tags: ["unchanged"]})        conn = -        build_conn() -        |> assign(:user, admin) +        conn          |> put_req_header("accept", "application/json")          |> put( -          "/api/pleroma/admin/users/tag?nicknames[]=#{user1.nickname}&nicknames[]=#{ -            user2.nickname -          }&tags[]=foo&tags[]=bar" +          "/api/pleroma/admin/users/tag?nicknames[]=#{user1.nickname}&nicknames[]=" <> +            "#{user2.nickname}&tags[]=foo&tags[]=bar"          )        %{conn: conn, user1: user1, user2: user2, user3: user3} @@ -147,12 +413,25 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do      test "it appends specified tags to users with specified nicknames", %{        conn: conn, +      admin: admin,        user1: user1,        user2: user2      } do        assert json_response(conn, :no_content)        assert User.get_cached_by_id(user1.id).tags == ["x", "foo", "bar"]        assert User.get_cached_by_id(user2.id).tags == ["y", "foo", "bar"] + +      log_entry = Repo.one(ModerationLog) + +      users = +        [user1.nickname, user2.nickname] +        |> Enum.map(&"@#{&1}") +        |> Enum.join(", ") + +      tags = ["foo", "bar"] |> Enum.join(", ") + +      assert ModerationLog.get_log_entry_message(log_entry) == +               "@#{admin.nickname} added tags: #{tags} to users: #{users}"      end      test "it does not modify tags of not specified users", %{conn: conn, user3: user3} do @@ -162,20 +441,17 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do    end    describe "DELETE /api/pleroma/admin/users/tag" do -    setup do -      admin = insert(:user, info: %{is_admin: true}) +    setup %{conn: conn} do        user1 = insert(:user, %{tags: ["x"]})        user2 = insert(:user, %{tags: ["y", "z"]})        user3 = insert(:user, %{tags: ["unchanged"]})        conn = -        build_conn() -        |> assign(:user, admin) +        conn          |> put_req_header("accept", "application/json")          |> delete( -          "/api/pleroma/admin/users/tag?nicknames[]=#{user1.nickname}&nicknames[]=#{ -            user2.nickname -          }&tags[]=x&tags[]=z" +          "/api/pleroma/admin/users/tag?nicknames[]=#{user1.nickname}&nicknames[]=" <> +            "#{user2.nickname}&tags[]=x&tags[]=z"          )        %{conn: conn, user1: user1, user2: user2, user3: user3} @@ -183,12 +459,25 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do      test "it removes specified tags from users with specified nicknames", %{        conn: conn, +      admin: admin,        user1: user1,        user2: user2      } do        assert json_response(conn, :no_content)        assert User.get_cached_by_id(user1.id).tags == []        assert User.get_cached_by_id(user2.id).tags == ["y"] + +      log_entry = Repo.one(ModerationLog) + +      users = +        [user1.nickname, user2.nickname] +        |> Enum.map(&"@#{&1}") +        |> Enum.join(", ") + +      tags = ["x", "z"] |> Enum.join(", ") + +      assert ModerationLog.get_log_entry_message(log_entry) == +               "@#{admin.nickname} removed tags: #{tags} from users: #{users}"      end      test "it does not modify tags of not specified users", %{conn: conn, user3: user3} do @@ -198,12 +487,9 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do    end    describe "/api/pleroma/admin/users/:nickname/permission_group" do -    test "GET is giving user_info" do -      admin = insert(:user, info: %{is_admin: true}) - +    test "GET is giving user_info", %{admin: admin, conn: conn} do        conn = -        build_conn() -        |> assign(:user, admin) +        conn          |> put_req_header("accept", "application/json")          |> get("/api/pleroma/admin/users/#{admin.nickname}/permission_group/") @@ -213,115 +499,106 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do               }      end -    test "/:right POST, can add to a permission group" do -      admin = insert(:user, info: %{is_admin: true}) +    test "/:right POST, can add to a permission group", %{admin: admin, conn: conn} do        user = insert(:user)        conn = -        build_conn() -        |> assign(:user, admin) +        conn          |> put_req_header("accept", "application/json")          |> post("/api/pleroma/admin/users/#{user.nickname}/permission_group/admin")        assert json_response(conn, 200) == %{                 "is_admin" => true               } -    end - -    test "/:right DELETE, can remove from a permission group" do -      admin = insert(:user, info: %{is_admin: true}) -      user = insert(:user, info: %{is_admin: true}) -      conn = -        build_conn() -        |> assign(:user, admin) -        |> put_req_header("accept", "application/json") -        |> delete("/api/pleroma/admin/users/#{user.nickname}/permission_group/admin") +      log_entry = Repo.one(ModerationLog) -      assert json_response(conn, 200) == %{ -               "is_admin" => false -             } +      assert ModerationLog.get_log_entry_message(log_entry) == +               "@#{admin.nickname} made @#{user.nickname} admin"      end -  end -  describe "PUT /api/pleroma/admin/users/:nickname/activation_status" do -    setup %{conn: conn} do -      admin = insert(:user, info: %{is_admin: true}) +    test "/:right POST, can add to a permission group (multiple)", %{admin: admin, conn: conn} do +      user_one = insert(:user) +      user_two = insert(:user)        conn =          conn -        |> assign(:user, admin)          |> put_req_header("accept", "application/json") +        |> post("/api/pleroma/admin/users/permission_group/admin", %{ +          nicknames: [user_one.nickname, user_two.nickname] +        }) -      %{conn: conn} -    end - -    test "deactivates the user", %{conn: conn} do -      user = insert(:user) +      assert json_response(conn, 200) == %{"is_admin" => true} -      conn = -        conn -        |> put("/api/pleroma/admin/users/#{user.nickname}/activation_status", %{status: false}) +      log_entry = Repo.one(ModerationLog) -      user = User.get_cached_by_id(user.id) -      assert user.info.deactivated == true -      assert json_response(conn, :no_content) +      assert ModerationLog.get_log_entry_message(log_entry) == +               "@#{admin.nickname} made @#{user_one.nickname}, @#{user_two.nickname} admin"      end -    test "activates the user", %{conn: conn} do -      user = insert(:user, info: %{deactivated: true}) +    test "/:right DELETE, can remove from a permission group", %{admin: admin, conn: conn} do +      user = insert(:user, is_admin: true)        conn =          conn -        |> put("/api/pleroma/admin/users/#{user.nickname}/activation_status", %{status: true}) +        |> put_req_header("accept", "application/json") +        |> delete("/api/pleroma/admin/users/#{user.nickname}/permission_group/admin") -      user = User.get_cached_by_id(user.id) -      assert user.info.deactivated == false -      assert json_response(conn, :no_content) +      assert json_response(conn, 200) == %{"is_admin" => false} + +      log_entry = Repo.one(ModerationLog) + +      assert ModerationLog.get_log_entry_message(log_entry) == +               "@#{admin.nickname} revoked admin role from @#{user.nickname}"      end -    test "returns 403 when requested by a non-admin", %{conn: conn} do -      user = insert(:user) +    test "/:right DELETE, can remove from a permission group (multiple)", %{ +      admin: admin, +      conn: conn +    } do +      user_one = insert(:user, is_admin: true) +      user_two = insert(:user, is_admin: true)        conn =          conn -        |> assign(:user, user) -        |> put("/api/pleroma/admin/users/#{user.nickname}/activation_status", %{status: false}) +        |> put_req_header("accept", "application/json") +        |> delete("/api/pleroma/admin/users/permission_group/admin", %{ +          nicknames: [user_one.nickname, user_two.nickname] +        }) -      assert json_response(conn, :forbidden) +      assert json_response(conn, 200) == %{"is_admin" => false} + +      log_entry = Repo.one(ModerationLog) + +      assert ModerationLog.get_log_entry_message(log_entry) == +               "@#{admin.nickname} revoked admin role from @#{user_one.nickname}, @#{ +                 user_two.nickname +               }"      end    end    describe "POST /api/pleroma/admin/email_invite, with valid config" do -    setup do -      registrations_open = Pleroma.Config.get([:instance, :registrations_open]) -      invites_enabled = Pleroma.Config.get([:instance, :invites_enabled]) +    clear_config([:instance, :registrations_open]) do        Pleroma.Config.put([:instance, :registrations_open], false) -      Pleroma.Config.put([:instance, :invites_enabled], true) - -      on_exit(fn -> -        Pleroma.Config.put([:instance, :registrations_open], registrations_open) -        Pleroma.Config.put([:instance, :invites_enabled], invites_enabled) -        :ok -      end) +    end -      [user: insert(:user, info: %{is_admin: true})] +    clear_config([:instance, :invites_enabled]) do +      Pleroma.Config.put([:instance, :invites_enabled], true)      end -    test "sends invitation and returns 204", %{conn: conn, user: user} do +    test "sends invitation and returns 204", %{admin: admin, conn: conn} do        recipient_email = "foo@bar.com"        recipient_name = "J. D."        conn = -        conn -        |> assign(:user, user) -        |> post( +        post( +          conn,            "/api/pleroma/admin/users/email_invite?email=#{recipient_email}&name=#{recipient_name}"          )        assert json_response(conn, :no_content) -      token_record = List.last(Pleroma.Repo.all(Pleroma.UserInviteToken)) +      token_record = List.last(Repo.all(Pleroma.UserInviteToken))        assert token_record        refute token_record.used @@ -330,7 +607,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do        email =          Pleroma.Emails.UserEmail.user_invitation_email( -          user, +          admin,            token_record,            recipient_email,            recipient_name @@ -343,12 +620,14 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do        )      end -    test "it returns 403 if requested by a non-admin", %{conn: conn} do +    test "it returns 403 if requested by a non-admin" do        non_admin_user = insert(:user) +      token = insert(:oauth_token, user: non_admin_user)        conn = -        conn +        build_conn()          |> assign(:user, non_admin_user) +        |> assign(:token, token)          |> post("/api/pleroma/admin/users/email_invite?email=foo@bar.com&name=JD")        assert json_response(conn, :forbidden) @@ -356,87 +635,42 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do    end    describe "POST /api/pleroma/admin/users/email_invite, with invalid config" do -    setup do -      [user: insert(:user, info: %{is_admin: true})] -    end +    clear_config([:instance, :registrations_open]) +    clear_config([:instance, :invites_enabled]) -    test "it returns 500 if `invites_enabled` is not enabled", %{conn: conn, user: user} do -      registrations_open = Pleroma.Config.get([:instance, :registrations_open]) -      invites_enabled = Pleroma.Config.get([:instance, :invites_enabled]) +    test "it returns 500 if `invites_enabled` is not enabled", %{conn: conn} do        Pleroma.Config.put([:instance, :registrations_open], false)        Pleroma.Config.put([:instance, :invites_enabled], false) -      on_exit(fn -> -        Pleroma.Config.put([:instance, :registrations_open], registrations_open) -        Pleroma.Config.put([:instance, :invites_enabled], invites_enabled) -        :ok -      end) - -      conn = -        conn -        |> assign(:user, user) -        |> post("/api/pleroma/admin/users/email_invite?email=foo@bar.com&name=JD") +      conn = post(conn, "/api/pleroma/admin/users/email_invite?email=foo@bar.com&name=JD")        assert json_response(conn, :internal_server_error)      end -    test "it returns 500 if `registrations_open` is enabled", %{conn: conn, user: user} do -      registrations_open = Pleroma.Config.get([:instance, :registrations_open]) -      invites_enabled = Pleroma.Config.get([:instance, :invites_enabled]) +    test "it returns 500 if `registrations_open` is enabled", %{conn: conn} do        Pleroma.Config.put([:instance, :registrations_open], true)        Pleroma.Config.put([:instance, :invites_enabled], true) -      on_exit(fn -> -        Pleroma.Config.put([:instance, :registrations_open], registrations_open) -        Pleroma.Config.put([:instance, :invites_enabled], invites_enabled) -        :ok -      end) - -      conn = -        conn -        |> assign(:user, user) -        |> post("/api/pleroma/admin/users/email_invite?email=foo@bar.com&name=JD") +      conn = post(conn, "/api/pleroma/admin/users/email_invite?email=foo@bar.com&name=JD")        assert json_response(conn, :internal_server_error)      end    end -  test "/api/pleroma/admin/users/invite_token" do -    admin = insert(:user, info: %{is_admin: true}) - -    conn = -      build_conn() -      |> assign(:user, admin) -      |> put_req_header("accept", "application/json") -      |> get("/api/pleroma/admin/users/invite_token") - -    assert conn.status == 200 -  end - -  test "/api/pleroma/admin/users/:nickname/password_reset" do -    admin = insert(:user, info: %{is_admin: true}) +  test "/api/pleroma/admin/users/:nickname/password_reset", %{conn: conn} do      user = insert(:user)      conn = -      build_conn() -      |> assign(:user, admin) +      conn        |> put_req_header("accept", "application/json")        |> get("/api/pleroma/admin/users/#{user.nickname}/password_reset") -    assert conn.status == 200 +    resp = json_response(conn, 200) + +    assert Regex.match?(~r/(http:\/\/|https:\/\/)/, resp["link"])    end    describe "GET /api/pleroma/admin/users" do -    setup do -      admin = insert(:user, info: %{is_admin: true}) - -      conn = -        build_conn() -        |> assign(:user, admin) - -      {:ok, conn: conn, admin: admin} -    end -      test "renders users array for the first page", %{conn: conn, admin: admin} do        user = insert(:user, local: false, tags: ["foo", "bar"])        conn = get(conn, "/api/pleroma/admin/users?page=1") @@ -444,24 +678,26 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do        users =          [            %{ -            "deactivated" => admin.info.deactivated, +            "deactivated" => admin.deactivated,              "id" => admin.id,              "nickname" => admin.nickname,              "roles" => %{"admin" => true, "moderator" => false},              "local" => true,              "tags" => [],              "avatar" => User.avatar_url(admin) |> MediaProxy.url(), -            "display_name" => HTML.strip_tags(admin.name || admin.nickname) +            "display_name" => HTML.strip_tags(admin.name || admin.nickname), +            "confirmation_pending" => false            },            %{ -            "deactivated" => user.info.deactivated, +            "deactivated" => user.deactivated,              "id" => user.id,              "nickname" => user.nickname,              "roles" => %{"admin" => false, "moderator" => false},              "local" => false,              "tags" => ["foo", "bar"],              "avatar" => User.avatar_url(user) |> MediaProxy.url(), -            "display_name" => HTML.strip_tags(user.name || user.nickname) +            "display_name" => HTML.strip_tags(user.name || user.nickname), +            "confirmation_pending" => false            }          ]          |> Enum.sort_by(& &1["nickname"]) @@ -495,14 +731,15 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do                 "page_size" => 50,                 "users" => [                   %{ -                   "deactivated" => user.info.deactivated, +                   "deactivated" => user.deactivated,                     "id" => user.id,                     "nickname" => user.nickname,                     "roles" => %{"admin" => false, "moderator" => false},                     "local" => true,                     "tags" => [],                     "avatar" => User.avatar_url(user) |> MediaProxy.url(), -                   "display_name" => HTML.strip_tags(user.name || user.nickname) +                   "display_name" => HTML.strip_tags(user.name || user.nickname), +                   "confirmation_pending" => false                   }                 ]               } @@ -519,14 +756,15 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do                 "page_size" => 50,                 "users" => [                   %{ -                   "deactivated" => user.info.deactivated, +                   "deactivated" => user.deactivated,                     "id" => user.id,                     "nickname" => user.nickname,                     "roles" => %{"admin" => false, "moderator" => false},                     "local" => true,                     "tags" => [],                     "avatar" => User.avatar_url(user) |> MediaProxy.url(), -                   "display_name" => HTML.strip_tags(user.name || user.nickname) +                   "display_name" => HTML.strip_tags(user.name || user.nickname), +                   "confirmation_pending" => false                   }                 ]               } @@ -543,14 +781,15 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do                 "page_size" => 50,                 "users" => [                   %{ -                   "deactivated" => user.info.deactivated, +                   "deactivated" => user.deactivated,                     "id" => user.id,                     "nickname" => user.nickname,                     "roles" => %{"admin" => false, "moderator" => false},                     "local" => true,                     "tags" => [],                     "avatar" => User.avatar_url(user) |> MediaProxy.url(), -                   "display_name" => HTML.strip_tags(user.name || user.nickname) +                   "display_name" => HTML.strip_tags(user.name || user.nickname), +                   "confirmation_pending" => false                   }                 ]               } @@ -567,14 +806,15 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do                 "page_size" => 50,                 "users" => [                   %{ -                   "deactivated" => user.info.deactivated, +                   "deactivated" => user.deactivated,                     "id" => user.id,                     "nickname" => user.nickname,                     "roles" => %{"admin" => false, "moderator" => false},                     "local" => true,                     "tags" => [],                     "avatar" => User.avatar_url(user) |> MediaProxy.url(), -                   "display_name" => HTML.strip_tags(user.name || user.nickname) +                   "display_name" => HTML.strip_tags(user.name || user.nickname), +                   "confirmation_pending" => false                   }                 ]               } @@ -591,14 +831,15 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do                 "page_size" => 50,                 "users" => [                   %{ -                   "deactivated" => user.info.deactivated, +                   "deactivated" => user.deactivated,                     "id" => user.id,                     "nickname" => user.nickname,                     "roles" => %{"admin" => false, "moderator" => false},                     "local" => true,                     "tags" => [],                     "avatar" => User.avatar_url(user) |> MediaProxy.url(), -                   "display_name" => HTML.strip_tags(user.name || user.nickname) +                   "display_name" => HTML.strip_tags(user.name || user.nickname), +                   "confirmation_pending" => false                   }                 ]               } @@ -615,14 +856,15 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do                 "page_size" => 1,                 "users" => [                   %{ -                   "deactivated" => user.info.deactivated, +                   "deactivated" => user.deactivated,                     "id" => user.id,                     "nickname" => user.nickname,                     "roles" => %{"admin" => false, "moderator" => false},                     "local" => true,                     "tags" => [],                     "avatar" => User.avatar_url(user) |> MediaProxy.url(), -                   "display_name" => HTML.strip_tags(user.name || user.nickname) +                   "display_name" => HTML.strip_tags(user.name || user.nickname), +                   "confirmation_pending" => false                   }                 ]               } @@ -634,21 +876,23 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do                 "page_size" => 1,                 "users" => [                   %{ -                   "deactivated" => user2.info.deactivated, +                   "deactivated" => user2.deactivated,                     "id" => user2.id,                     "nickname" => user2.nickname,                     "roles" => %{"admin" => false, "moderator" => false},                     "local" => true,                     "tags" => [],                     "avatar" => User.avatar_url(user2) |> MediaProxy.url(), -                   "display_name" => HTML.strip_tags(user2.name || user2.nickname) +                   "display_name" => HTML.strip_tags(user2.name || user2.nickname), +                   "confirmation_pending" => false                   }                 ]               }      end      test "only local users" do -      admin = insert(:user, info: %{is_admin: true}, nickname: "john") +      admin = insert(:user, is_admin: true, nickname: "john") +      token = insert(:oauth_admin_token, user: admin)        user = insert(:user, nickname: "bob")        insert(:user, nickname: "bobb", local: false) @@ -656,6 +900,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do        conn =          build_conn()          |> assign(:user, admin) +        |> assign(:token, token)          |> get("/api/pleroma/admin/users?query=bo&filters=local")        assert json_response(conn, 200) == %{ @@ -663,51 +908,51 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do                 "page_size" => 50,                 "users" => [                   %{ -                   "deactivated" => user.info.deactivated, +                   "deactivated" => user.deactivated,                     "id" => user.id,                     "nickname" => user.nickname,                     "roles" => %{"admin" => false, "moderator" => false},                     "local" => true,                     "tags" => [],                     "avatar" => User.avatar_url(user) |> MediaProxy.url(), -                   "display_name" => HTML.strip_tags(user.name || user.nickname) +                   "display_name" => HTML.strip_tags(user.name || user.nickname), +                   "confirmation_pending" => false                   }                 ]               }      end -    test "only local users with no query", %{admin: old_admin} do -      admin = insert(:user, info: %{is_admin: true}, nickname: "john") +    test "only local users with no query", %{conn: conn, admin: old_admin} do +      admin = insert(:user, is_admin: true, nickname: "john")        user = insert(:user, nickname: "bob")        insert(:user, nickname: "bobb", local: false) -      conn = -        build_conn() -        |> assign(:user, admin) -        |> get("/api/pleroma/admin/users?filters=local") +      conn = get(conn, "/api/pleroma/admin/users?filters=local")        users =          [            %{ -            "deactivated" => user.info.deactivated, +            "deactivated" => user.deactivated,              "id" => user.id,              "nickname" => user.nickname,              "roles" => %{"admin" => false, "moderator" => false},              "local" => true,              "tags" => [],              "avatar" => User.avatar_url(user) |> MediaProxy.url(), -            "display_name" => HTML.strip_tags(user.name || user.nickname) +            "display_name" => HTML.strip_tags(user.name || user.nickname), +            "confirmation_pending" => false            },            %{ -            "deactivated" => admin.info.deactivated, +            "deactivated" => admin.deactivated,              "id" => admin.id,              "nickname" => admin.nickname,              "roles" => %{"admin" => true, "moderator" => false},              "local" => true,              "tags" => [],              "avatar" => User.avatar_url(admin) |> MediaProxy.url(), -            "display_name" => HTML.strip_tags(admin.name || admin.nickname) +            "display_name" => HTML.strip_tags(admin.name || admin.nickname), +            "confirmation_pending" => false            },            %{              "deactivated" => false, @@ -717,7 +962,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do              "roles" => %{"admin" => true, "moderator" => false},              "tags" => [],              "avatar" => User.avatar_url(old_admin) |> MediaProxy.url(), -            "display_name" => HTML.strip_tags(old_admin.name || old_admin.nickname) +            "display_name" => HTML.strip_tags(old_admin.name || old_admin.nickname), +            "confirmation_pending" => false            }          ]          |> Enum.sort_by(& &1["nickname"]) @@ -730,7 +976,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do      end      test "load only admins", %{conn: conn, admin: admin} do -      second_admin = insert(:user, info: %{is_admin: true}) +      second_admin = insert(:user, is_admin: true)        insert(:user)        insert(:user) @@ -746,7 +992,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do              "local" => admin.local,              "tags" => [],              "avatar" => User.avatar_url(admin) |> MediaProxy.url(), -            "display_name" => HTML.strip_tags(admin.name || admin.nickname) +            "display_name" => HTML.strip_tags(admin.name || admin.nickname), +            "confirmation_pending" => false            },            %{              "deactivated" => false, @@ -756,7 +1003,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do              "local" => second_admin.local,              "tags" => [],              "avatar" => User.avatar_url(second_admin) |> MediaProxy.url(), -            "display_name" => HTML.strip_tags(second_admin.name || second_admin.nickname) +            "display_name" => HTML.strip_tags(second_admin.name || second_admin.nickname), +            "confirmation_pending" => false            }          ]          |> Enum.sort_by(& &1["nickname"]) @@ -769,7 +1017,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do      end      test "load only moderators", %{conn: conn} do -      moderator = insert(:user, info: %{is_moderator: true}) +      moderator = insert(:user, is_moderator: true)        insert(:user)        insert(:user) @@ -787,7 +1035,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do                     "local" => moderator.local,                     "tags" => [],                     "avatar" => User.avatar_url(moderator) |> MediaProxy.url(), -                   "display_name" => HTML.strip_tags(moderator.name || moderator.nickname) +                   "display_name" => HTML.strip_tags(moderator.name || moderator.nickname), +                   "confirmation_pending" => false                   }                 ]               } @@ -811,7 +1060,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do              "local" => user1.local,              "tags" => ["first"],              "avatar" => User.avatar_url(user1) |> MediaProxy.url(), -            "display_name" => HTML.strip_tags(user1.name || user1.nickname) +            "display_name" => HTML.strip_tags(user1.name || user1.nickname), +            "confirmation_pending" => false            },            %{              "deactivated" => false, @@ -821,7 +1071,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do              "local" => user2.local,              "tags" => ["second"],              "avatar" => User.avatar_url(user2) |> MediaProxy.url(), -            "display_name" => HTML.strip_tags(user2.name || user2.nickname) +            "display_name" => HTML.strip_tags(user2.name || user2.nickname), +            "confirmation_pending" => false            }          ]          |> Enum.sort_by(& &1["nickname"]) @@ -834,15 +1085,17 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do      end      test "it works with multiple filters" do -      admin = insert(:user, nickname: "john", info: %{is_admin: true}) -      user = insert(:user, nickname: "bob", local: false, info: %{deactivated: true}) +      admin = insert(:user, nickname: "john", is_admin: true) +      token = insert(:oauth_admin_token, user: admin) +      user = insert(:user, nickname: "bob", local: false, deactivated: true) -      insert(:user, nickname: "ken", local: true, info: %{deactivated: true}) -      insert(:user, nickname: "bobb", local: false, info: %{deactivated: false}) +      insert(:user, nickname: "ken", local: true, deactivated: true) +      insert(:user, nickname: "bobb", local: false, deactivated: false)        conn =          build_conn()          |> assign(:user, admin) +        |> assign(:token, token)          |> get("/api/pleroma/admin/users?filters=deactivated,external")        assert json_response(conn, 200) == %{ @@ -850,58 +1103,115 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do                 "page_size" => 50,                 "users" => [                   %{ -                   "deactivated" => user.info.deactivated, +                   "deactivated" => user.deactivated,                     "id" => user.id,                     "nickname" => user.nickname,                     "roles" => %{"admin" => false, "moderator" => false},                     "local" => user.local,                     "tags" => [],                     "avatar" => User.avatar_url(user) |> MediaProxy.url(), -                   "display_name" => HTML.strip_tags(user.name || user.nickname) +                   "display_name" => HTML.strip_tags(user.name || user.nickname), +                   "confirmation_pending" => false +                 } +               ] +             } +    end + +    test "it omits relay user", %{admin: admin, conn: conn} do +      assert %User{} = Relay.get_actor() + +      conn = get(conn, "/api/pleroma/admin/users") + +      assert json_response(conn, 200) == %{ +               "count" => 1, +               "page_size" => 50, +               "users" => [ +                 %{ +                   "deactivated" => admin.deactivated, +                   "id" => admin.id, +                   "nickname" => admin.nickname, +                   "roles" => %{"admin" => true, "moderator" => false}, +                   "local" => true, +                   "tags" => [], +                   "avatar" => User.avatar_url(admin) |> MediaProxy.url(), +                   "display_name" => HTML.strip_tags(admin.name || admin.nickname), +                   "confirmation_pending" => false                   }                 ]               }      end    end -  test "PATCH /api/pleroma/admin/users/:nickname/toggle_activation" do -    admin = insert(:user, info: %{is_admin: true}) -    user = insert(:user) +  test "PATCH /api/pleroma/admin/users/activate", %{admin: admin, conn: conn} do +    user_one = insert(:user, deactivated: true) +    user_two = insert(:user, deactivated: true)      conn = -      build_conn() -      |> assign(:user, admin) -      |> patch("/api/pleroma/admin/users/#{user.nickname}/toggle_activation") +      patch( +        conn, +        "/api/pleroma/admin/users/activate", +        %{nicknames: [user_one.nickname, user_two.nickname]} +      ) + +    response = json_response(conn, 200) +    assert Enum.map(response["users"], & &1["deactivated"]) == [false, false] + +    log_entry = Repo.one(ModerationLog) + +    assert ModerationLog.get_log_entry_message(log_entry) == +             "@#{admin.nickname} activated users: @#{user_one.nickname}, @#{user_two.nickname}" +  end + +  test "PATCH /api/pleroma/admin/users/deactivate", %{admin: admin, conn: conn} do +    user_one = insert(:user, deactivated: false) +    user_two = insert(:user, deactivated: false) + +    conn = +      patch( +        conn, +        "/api/pleroma/admin/users/deactivate", +        %{nicknames: [user_one.nickname, user_two.nickname]} +      ) + +    response = json_response(conn, 200) +    assert Enum.map(response["users"], & &1["deactivated"]) == [true, true] + +    log_entry = Repo.one(ModerationLog) + +    assert ModerationLog.get_log_entry_message(log_entry) == +             "@#{admin.nickname} deactivated users: @#{user_one.nickname}, @#{user_two.nickname}" +  end + +  test "PATCH /api/pleroma/admin/users/:nickname/toggle_activation", %{admin: admin, conn: conn} do +    user = insert(:user) + +    conn = patch(conn, "/api/pleroma/admin/users/#{user.nickname}/toggle_activation")      assert json_response(conn, 200) ==               %{ -               "deactivated" => !user.info.deactivated, +               "deactivated" => !user.deactivated,                 "id" => user.id,                 "nickname" => user.nickname,                 "roles" => %{"admin" => false, "moderator" => false},                 "local" => true,                 "tags" => [],                 "avatar" => User.avatar_url(user) |> MediaProxy.url(), -               "display_name" => HTML.strip_tags(user.name || user.nickname) +               "display_name" => HTML.strip_tags(user.name || user.nickname), +               "confirmation_pending" => false               } -  end - -  describe "GET /api/pleroma/admin/users/invite_token" do -    setup do -      admin = insert(:user, info: %{is_admin: true}) -      conn = -        build_conn() -        |> assign(:user, admin) +    log_entry = Repo.one(ModerationLog) -      {:ok, conn: conn} -    end +    assert ModerationLog.get_log_entry_message(log_entry) == +             "@#{admin.nickname} deactivated users: @#{user.nickname}" +  end +  describe "POST /api/pleroma/admin/users/invite_token" do      test "without options", %{conn: conn} do -      conn = get(conn, "/api/pleroma/admin/users/invite_token") +      conn = post(conn, "/api/pleroma/admin/users/invite_token") -      token = json_response(conn, 200) -      invite = UserInviteToken.find_by_token!(token) +      invite_json = json_response(conn, 200) +      invite = UserInviteToken.find_by_token!(invite_json["token"])        refute invite.used        refute invite.expires_at        refute invite.max_use @@ -910,12 +1220,12 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do      test "with expires_at", %{conn: conn} do        conn = -        get(conn, "/api/pleroma/admin/users/invite_token", %{ -          "invite" => %{"expires_at" => Date.to_string(Date.utc_today())} +        post(conn, "/api/pleroma/admin/users/invite_token", %{ +          "expires_at" => Date.to_string(Date.utc_today())          }) -      token = json_response(conn, 200) -      invite = UserInviteToken.find_by_token!(token) +      invite_json = json_response(conn, 200) +      invite = UserInviteToken.find_by_token!(invite_json["token"])        refute invite.used        assert invite.expires_at == Date.utc_today() @@ -924,13 +1234,10 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do      end      test "with max_use", %{conn: conn} do -      conn = -        get(conn, "/api/pleroma/admin/users/invite_token", %{ -          "invite" => %{"max_use" => 150} -        }) +      conn = post(conn, "/api/pleroma/admin/users/invite_token", %{"max_use" => 150}) -      token = json_response(conn, 200) -      invite = UserInviteToken.find_by_token!(token) +      invite_json = json_response(conn, 200) +      invite = UserInviteToken.find_by_token!(invite_json["token"])        refute invite.used        refute invite.expires_at        assert invite.max_use == 150 @@ -939,12 +1246,13 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do      test "with max use and expires_at", %{conn: conn} do        conn = -        get(conn, "/api/pleroma/admin/users/invite_token", %{ -          "invite" => %{"max_use" => 150, "expires_at" => Date.to_string(Date.utc_today())} +        post(conn, "/api/pleroma/admin/users/invite_token", %{ +          "max_use" => 150, +          "expires_at" => Date.to_string(Date.utc_today())          }) -      token = json_response(conn, 200) -      invite = UserInviteToken.find_by_token!(token) +      invite_json = json_response(conn, 200) +      invite = UserInviteToken.find_by_token!(invite_json["token"])        refute invite.used        assert invite.expires_at == Date.utc_today()        assert invite.max_use == 150 @@ -953,16 +1261,6 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do    end    describe "GET /api/pleroma/admin/users/invites" do -    setup do -      admin = insert(:user, info: %{is_admin: true}) - -      conn = -        build_conn() -        |> assign(:user, admin) - -      {:ok, conn: conn} -    end -      test "no invites", %{conn: conn} do        conn = get(conn, "/api/pleroma/admin/users/invites") @@ -991,14 +1289,10 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do    end    describe "POST /api/pleroma/admin/users/revoke_invite" do -    test "with token" do -      admin = insert(:user, info: %{is_admin: true}) +    test "with token", %{conn: conn} do        {:ok, invite} = UserInviteToken.create_invite() -      conn = -        build_conn() -        |> assign(:user, admin) -        |> post("/api/pleroma/admin/users/revoke_invite", %{"token" => invite.token}) +      conn = post(conn, "/api/pleroma/admin/users/revoke_invite", %{"token" => invite.token})        assert json_response(conn, 200) == %{                 "expires_at" => nil, @@ -1010,15 +1304,15 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do                 "uses" => 0               }      end -  end -  describe "GET /api/pleroma/admin/reports/:id" do -    setup %{conn: conn} do -      admin = insert(:user, info: %{is_admin: true}) +    test "with invalid token", %{conn: conn} do +      conn = post(conn, "/api/pleroma/admin/users/revoke_invite", %{"token" => "foo"}) -      %{conn: assign(conn, :user, admin)} +      assert json_response(conn, :not_found) == "Not found"      end +  end +  describe "GET /api/pleroma/admin/reports/:id" do      test "returns report by its id", %{conn: conn} do        [reporter, target_user] = insert_pair(:user)        activity = insert(:note_activity, user: target_user) @@ -1045,9 +1339,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do      end    end -  describe "PUT /api/pleroma/admin/reports/:id" do -    setup %{conn: conn} do -      admin = insert(:user, info: %{is_admin: true}) +  describe "PATCH /api/pleroma/admin/reports" do +    setup do        [reporter, target_user] = insert_pair(:user)        activity = insert(:note_activity, user: target_user) @@ -1058,51 +1351,134 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do            "status_ids" => [activity.id]          }) -      %{conn: assign(conn, :user, admin), id: report_id} +      {:ok, %{id: second_report_id}} = +        CommonAPI.report(reporter, %{ +          "account_id" => target_user.id, +          "comment" => "I feel very offended", +          "status_ids" => [activity.id] +        }) + +      %{ +        id: report_id, +        second_report_id: second_report_id +      }      end -    test "mark report as resolved", %{conn: conn, id: id} do +    test "requires admin:write:reports scope", %{conn: conn, id: id, admin: admin} do +      read_token = insert(:oauth_token, user: admin, scopes: ["admin:read"]) +      write_token = insert(:oauth_token, user: admin, scopes: ["admin:write:reports"]) +        response =          conn -        |> put("/api/pleroma/admin/reports/#{id}", %{"state" => "resolved"}) -        |> json_response(:ok) +        |> assign(:token, read_token) +        |> patch("/api/pleroma/admin/reports", %{ +          "reports" => [%{"state" => "resolved", "id" => id}] +        }) +        |> json_response(403) + +      assert response == %{ +               "error" => "Insufficient permissions: admin:write:reports." +             } -      assert response["state"] == "resolved" +      conn +      |> assign(:token, write_token) +      |> patch("/api/pleroma/admin/reports", %{ +        "reports" => [%{"state" => "resolved", "id" => id}] +      }) +      |> json_response(:no_content)      end -    test "closes report", %{conn: conn, id: id} do -      response = -        conn -        |> put("/api/pleroma/admin/reports/#{id}", %{"state" => "closed"}) -        |> json_response(:ok) +    test "mark report as resolved", %{conn: conn, id: id, admin: admin} do +      conn +      |> patch("/api/pleroma/admin/reports", %{ +        "reports" => [ +          %{"state" => "resolved", "id" => id} +        ] +      }) +      |> json_response(:no_content) + +      activity = Activity.get_by_id(id) +      assert activity.data["state"] == "resolved" + +      log_entry = Repo.one(ModerationLog) + +      assert ModerationLog.get_log_entry_message(log_entry) == +               "@#{admin.nickname} updated report ##{id} with 'resolved' state" +    end + +    test "closes report", %{conn: conn, id: id, admin: admin} do +      conn +      |> patch("/api/pleroma/admin/reports", %{ +        "reports" => [ +          %{"state" => "closed", "id" => id} +        ] +      }) +      |> json_response(:no_content) + +      activity = Activity.get_by_id(id) +      assert activity.data["state"] == "closed" -      assert response["state"] == "closed" +      log_entry = Repo.one(ModerationLog) + +      assert ModerationLog.get_log_entry_message(log_entry) == +               "@#{admin.nickname} updated report ##{id} with 'closed' state"      end      test "returns 400 when state is unknown", %{conn: conn, id: id} do        conn =          conn -        |> put("/api/pleroma/admin/reports/#{id}", %{"state" => "test"}) +        |> patch("/api/pleroma/admin/reports", %{ +          "reports" => [ +            %{"state" => "test", "id" => id} +          ] +        }) -      assert json_response(conn, :bad_request) == "Unsupported state" +      assert hd(json_response(conn, :bad_request))["error"] == "Unsupported state"      end      test "returns 404 when report is not exist", %{conn: conn} do        conn =          conn -        |> put("/api/pleroma/admin/reports/test", %{"state" => "closed"}) +        |> patch("/api/pleroma/admin/reports", %{ +          "reports" => [ +            %{"state" => "closed", "id" => "test"} +          ] +        }) -      assert json_response(conn, :not_found) == "Not found" +      assert hd(json_response(conn, :bad_request))["error"] == "not_found"      end -  end -  describe "GET /api/pleroma/admin/reports" do -    setup %{conn: conn} do -      admin = insert(:user, info: %{is_admin: true}) +    test "updates state of multiple reports", %{ +      conn: conn, +      id: id, +      admin: admin, +      second_report_id: second_report_id +    } do +      conn +      |> patch("/api/pleroma/admin/reports", %{ +        "reports" => [ +          %{"state" => "resolved", "id" => id}, +          %{"state" => "closed", "id" => second_report_id} +        ] +      }) +      |> json_response(:no_content) -      %{conn: assign(conn, :user, admin)} +      activity = Activity.get_by_id(id) +      second_activity = Activity.get_by_id(second_report_id) +      assert activity.data["state"] == "resolved" +      assert second_activity.data["state"] == "closed" + +      [first_log_entry, second_log_entry] = Repo.all(ModerationLog) + +      assert ModerationLog.get_log_entry_message(first_log_entry) == +               "@#{admin.nickname} updated report ##{id} with 'resolved' state" + +      assert ModerationLog.get_log_entry_message(second_log_entry) == +               "@#{admin.nickname} updated report ##{second_report_id} with 'closed' state"      end +  end +  describe "GET /api/pleroma/admin/reports" do      test "returns empty response when no reports created", %{conn: conn} do        response =          conn @@ -1110,6 +1486,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do          |> json_response(:ok)        assert Enum.empty?(response["reports"]) +      assert response["total"] == 0      end      test "returns reports", %{conn: conn} do @@ -1132,6 +1509,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do        assert length(response["reports"]) == 1        assert report["id"] == report_id + +      assert response["total"] == 1      end      test "returns reports with specified state", %{conn: conn} do @@ -1165,6 +1544,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do        assert length(response["reports"]) == 1        assert open_report["id"] == first_report_id +      assert response["total"] == 1 +        response =          conn          |> get("/api/pleroma/admin/reports", %{ @@ -1177,6 +1558,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do        assert length(response["reports"]) == 1        assert closed_report["id"] == second_report_id +      assert response["total"] == 1 +        response =          conn          |> get("/api/pleroma/admin/reports", %{ @@ -1185,85 +1568,240 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do          |> json_response(:ok)        assert Enum.empty?(response["reports"]) +      assert response["total"] == 0      end      test "returns 403 when requested by a non-admin" do        user = insert(:user) +      token = insert(:oauth_token, user: user)        conn =          build_conn()          |> assign(:user, user) +        |> assign(:token, token)          |> get("/api/pleroma/admin/reports") -      assert json_response(conn, :forbidden) == %{"error" => "User is not admin."} +      assert json_response(conn, :forbidden) == +               %{"error" => "User is not an admin or OAuth admin scope is not granted."}      end      test "returns 403 when requested by anonymous" do -      conn = -        build_conn() -        |> get("/api/pleroma/admin/reports") +      conn = get(build_conn(), "/api/pleroma/admin/reports")        assert json_response(conn, :forbidden) == %{"error" => "Invalid credentials."}      end    end -  describe "POST /api/pleroma/admin/reports/:id/respond" do -    setup %{conn: conn} do -      admin = insert(:user, info: %{is_admin: true}) +  describe "GET /api/pleroma/admin/grouped_reports" do +    setup do +      [reporter, target_user] = insert_pair(:user) -      %{conn: assign(conn, :user, admin)} -    end +      date1 = (DateTime.to_unix(DateTime.utc_now()) + 1000) |> DateTime.from_unix!() +      date2 = (DateTime.to_unix(DateTime.utc_now()) + 2000) |> DateTime.from_unix!() +      date3 = (DateTime.to_unix(DateTime.utc_now()) + 3000) |> DateTime.from_unix!() -    test "returns created dm", %{conn: conn} do -      [reporter, target_user] = insert_pair(:user) -      activity = insert(:note_activity, user: target_user) +      first_status = +        insert(:note_activity, user: target_user, data_attrs: %{"published" => date1}) -      {:ok, %{id: report_id}} = +      second_status = +        insert(:note_activity, user: target_user, data_attrs: %{"published" => date2}) + +      third_status = +        insert(:note_activity, user: target_user, data_attrs: %{"published" => date3}) + +      {:ok, first_report} =          CommonAPI.report(reporter, %{            "account_id" => target_user.id, -          "comment" => "I feel offended", -          "status_ids" => [activity.id] +          "status_ids" => [first_status.id, second_status.id, third_status.id]          }) +      {:ok, second_report} = +        CommonAPI.report(reporter, %{ +          "account_id" => target_user.id, +          "status_ids" => [first_status.id, second_status.id] +        }) + +      {:ok, third_report} = +        CommonAPI.report(reporter, %{ +          "account_id" => target_user.id, +          "status_ids" => [first_status.id] +        }) + +      %{ +        first_status: Activity.get_by_ap_id_with_object(first_status.data["id"]), +        second_status: Activity.get_by_ap_id_with_object(second_status.data["id"]), +        third_status: Activity.get_by_ap_id_with_object(third_status.data["id"]), +        first_report: first_report, +        first_status_reports: [first_report, second_report, third_report], +        second_status_reports: [first_report, second_report], +        third_status_reports: [first_report], +        target_user: target_user, +        reporter: reporter +      } +    end + +    test "returns reports grouped by status", %{ +      conn: conn, +      first_status: first_status, +      second_status: second_status, +      third_status: third_status, +      first_status_reports: first_status_reports, +      second_status_reports: second_status_reports, +      third_status_reports: third_status_reports, +      target_user: target_user, +      reporter: reporter +    } do        response =          conn -        |> post("/api/pleroma/admin/reports/#{report_id}/respond", %{ -          "status" => "I will check it out" -        }) +        |> get("/api/pleroma/admin/grouped_reports") +        |> json_response(:ok) + +      assert length(response["reports"]) == 3 + +      first_group = Enum.find(response["reports"], &(&1["status"]["id"] == first_status.id)) + +      second_group = Enum.find(response["reports"], &(&1["status"]["id"] == second_status.id)) + +      third_group = Enum.find(response["reports"], &(&1["status"]["id"] == third_status.id)) + +      assert length(first_group["reports"]) == 3 +      assert length(second_group["reports"]) == 2 +      assert length(third_group["reports"]) == 1 + +      assert first_group["date"] == +               Enum.max_by(first_status_reports, fn act -> +                 NaiveDateTime.from_iso8601!(act.data["published"]) +               end).data["published"] + +      assert first_group["status"] == +               Map.put( +                 stringify_keys(StatusView.render("show.json", %{activity: first_status})), +                 "deleted", +                 false +               ) + +      assert(first_group["account"]["id"] == target_user.id) + +      assert length(first_group["actors"]) == 1 +      assert hd(first_group["actors"])["id"] == reporter.id + +      assert Enum.map(first_group["reports"], & &1["id"]) -- +               Enum.map(first_status_reports, & &1.id) == [] + +      assert second_group["date"] == +               Enum.max_by(second_status_reports, fn act -> +                 NaiveDateTime.from_iso8601!(act.data["published"]) +               end).data["published"] + +      assert second_group["status"] == +               Map.put( +                 stringify_keys(StatusView.render("show.json", %{activity: second_status})), +                 "deleted", +                 false +               ) + +      assert second_group["account"]["id"] == target_user.id + +      assert length(second_group["actors"]) == 1 +      assert hd(second_group["actors"])["id"] == reporter.id + +      assert Enum.map(second_group["reports"], & &1["id"]) -- +               Enum.map(second_status_reports, & &1.id) == [] + +      assert third_group["date"] == +               Enum.max_by(third_status_reports, fn act -> +                 NaiveDateTime.from_iso8601!(act.data["published"]) +               end).data["published"] + +      assert third_group["status"] == +               Map.put( +                 stringify_keys(StatusView.render("show.json", %{activity: third_status})), +                 "deleted", +                 false +               ) + +      assert third_group["account"]["id"] == target_user.id + +      assert length(third_group["actors"]) == 1 +      assert hd(third_group["actors"])["id"] == reporter.id + +      assert Enum.map(third_group["reports"], & &1["id"]) -- +               Enum.map(third_status_reports, & &1.id) == [] +    end + +    test "reopened report renders status data", %{ +      conn: conn, +      first_report: first_report, +      first_status: first_status +    } do +      {:ok, _} = CommonAPI.update_report_state(first_report.id, "resolved") + +      response = +        conn +        |> get("/api/pleroma/admin/grouped_reports")          |> json_response(:ok) -      recipients = Enum.map(response["mentions"], & &1["username"]) +      first_group = Enum.find(response["reports"], &(&1["status"]["id"] == first_status.id)) -      assert reporter.nickname in recipients -      assert response["content"] == "I will check it out" -      assert response["visibility"] == "direct" +      assert first_group["status"] == +               Map.put( +                 stringify_keys(StatusView.render("show.json", %{activity: first_status})), +                 "deleted", +                 false +               )      end -    test "returns 400 when status is missing", %{conn: conn} do -      conn = post(conn, "/api/pleroma/admin/reports/test/respond") +    test "reopened report does not render status data if status has been deleted", %{ +      conn: conn, +      first_report: first_report, +      first_status: first_status, +      target_user: target_user +    } do +      {:ok, _} = CommonAPI.update_report_state(first_report.id, "resolved") +      {:ok, _} = CommonAPI.delete(first_status.id, target_user) + +      refute Activity.get_by_ap_id(first_status.id) -      assert json_response(conn, :bad_request) == "Invalid parameters" +      response = +        conn +        |> get("/api/pleroma/admin/grouped_reports") +        |> json_response(:ok) + +      assert Enum.find(response["reports"], &(&1["status"]["deleted"] == true))["status"][ +               "deleted" +             ] == true + +      assert length(Enum.filter(response["reports"], &(&1["status"]["deleted"] == false))) == 2      end -    test "returns 404 when report id is invalid", %{conn: conn} do -      conn = -        post(conn, "/api/pleroma/admin/reports/test/respond", %{ -          "status" => "foo" -        }) +    test "account not empty if status was deleted", %{ +      conn: conn, +      first_report: first_report, +      first_status: first_status, +      target_user: target_user +    } do +      {:ok, _} = CommonAPI.update_report_state(first_report.id, "resolved") +      {:ok, _} = CommonAPI.delete(first_status.id, target_user) -      assert json_response(conn, :not_found) == "Not found" +      refute Activity.get_by_ap_id(first_status.id) + +      response = +        conn +        |> get("/api/pleroma/admin/grouped_reports") +        |> json_response(:ok) + +      assert Enum.find(response["reports"], &(&1["status"]["deleted"] == true))["account"]      end    end    describe "PUT /api/pleroma/admin/statuses/:id" do -    setup %{conn: conn} do -      admin = insert(:user, info: %{is_admin: true}) +    setup do        activity = insert(:note_activity) -      %{conn: assign(conn, :user, admin), id: activity.id} +      %{id: activity.id}      end -    test "toggle sensitive flag", %{conn: conn, id: id} do +    test "toggle sensitive flag", %{conn: conn, id: id, admin: admin} do        response =          conn          |> put("/api/pleroma/admin/statuses/#{id}", %{"sensitive" => "true"}) @@ -1271,6 +1809,11 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do        assert response["sensitive"] +      log_entry = Repo.one(ModerationLog) + +      assert ModerationLog.get_log_entry_message(log_entry) == +               "@#{admin.nickname} updated status ##{id}, set sensitive: 'true'" +        response =          conn          |> put("/api/pleroma/admin/statuses/#{id}", %{"sensitive" => "false"}) @@ -1279,7 +1822,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do        refute response["sensitive"]      end -    test "change visibility flag", %{conn: conn, id: id} do +    test "change visibility flag", %{conn: conn, id: id, admin: admin} do        response =          conn          |> put("/api/pleroma/admin/statuses/#{id}", %{"visibility" => "public"}) @@ -1287,6 +1830,11 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do        assert response["visibility"] == "public" +      log_entry = Repo.one(ModerationLog) + +      assert ModerationLog.get_log_entry_message(log_entry) == +               "@#{admin.nickname} updated status ##{id}, set visibility: 'public'" +        response =          conn          |> put("/api/pleroma/admin/statuses/#{id}", %{"visibility" => "private"}) @@ -1303,65 +1851,76 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do      end      test "returns 400 when visibility is unknown", %{conn: conn, id: id} do -      conn = -        conn -        |> put("/api/pleroma/admin/statuses/#{id}", %{"visibility" => "test"}) +      conn = put(conn, "/api/pleroma/admin/statuses/#{id}", %{"visibility" => "test"})        assert json_response(conn, :bad_request) == "Unsupported visibility"      end    end    describe "DELETE /api/pleroma/admin/statuses/:id" do -    setup %{conn: conn} do -      admin = insert(:user, info: %{is_admin: true}) +    setup do        activity = insert(:note_activity) -      %{conn: assign(conn, :user, admin), id: activity.id} +      %{id: activity.id}      end -    test "deletes status", %{conn: conn, id: id} do +    test "deletes status", %{conn: conn, id: id, admin: admin} do        conn        |> delete("/api/pleroma/admin/statuses/#{id}")        |> json_response(:ok)        refute Activity.get_by_id(id) + +      log_entry = Repo.one(ModerationLog) + +      assert ModerationLog.get_log_entry_message(log_entry) == +               "@#{admin.nickname} deleted status ##{id}"      end      test "returns error when status is not exist", %{conn: conn} do -      conn = -        conn -        |> delete("/api/pleroma/admin/statuses/test") +      conn = delete(conn, "/api/pleroma/admin/statuses/test")        assert json_response(conn, :bad_request) == "Could not delete"      end    end    describe "GET /api/pleroma/admin/config" do -    setup %{conn: conn} do -      admin = insert(:user, info: %{is_admin: true}) +    clear_config(:configurable_from_database) do +      Pleroma.Config.put(:configurable_from_database, true) +    end -      %{conn: assign(conn, :user, admin)} +    test "when configuration from database is off", %{conn: conn} do +      initial = Pleroma.Config.get(:configurable_from_database) +      Pleroma.Config.put(:configurable_from_database, false) +      on_exit(fn -> Pleroma.Config.put(:configurable_from_database, initial) end) +      conn = get(conn, "/api/pleroma/admin/config") + +      assert json_response(conn, 400) == +               "To use this endpoint you need to enable configuration from database."      end      test "without any settings in db", %{conn: conn} do        conn = get(conn, "/api/pleroma/admin/config") -      assert json_response(conn, 200) == %{"configs" => []} +      assert json_response(conn, 400) == +               "To use configuration from database migrate your settings to database."      end -    test "with settings in db", %{conn: conn} do +    test "with settings only in db", %{conn: conn} do        config1 = insert(:config)        config2 = insert(:config) -      conn = get(conn, "/api/pleroma/admin/config") +      conn = get(conn, "/api/pleroma/admin/config", %{"only_db" => true})        %{          "configs" => [            %{ +            "group" => ":pleroma",              "key" => key1,              "value" => _            },            %{ +            "group" => ":pleroma",              "key" => key2,              "value" => _            } @@ -1371,13 +1930,107 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do        assert key1 == config1.key        assert key2 == config2.key      end + +    test "db is added to settings that are in db", %{conn: conn} do +      _config = insert(:config, key: ":instance", value: ConfigDB.to_binary(name: "Some name")) + +      %{"configs" => configs} = +        conn +        |> get("/api/pleroma/admin/config") +        |> json_response(200) + +      [instance_config] = +        Enum.filter(configs, fn %{"group" => group, "key" => key} -> +          group == ":pleroma" and key == ":instance" +        end) + +      assert instance_config["db"] == [":name"] +    end + +    test "merged default setting with db settings", %{conn: conn} do +      config1 = insert(:config) +      config2 = insert(:config) + +      config3 = +        insert(:config, +          value: ConfigDB.to_binary(k1: :v1, k2: :v2) +        ) + +      %{"configs" => configs} = +        conn +        |> get("/api/pleroma/admin/config") +        |> json_response(200) + +      assert length(configs) > 3 + +      received_configs = +        Enum.filter(configs, fn %{"group" => group, "key" => key} -> +          group == ":pleroma" and key in [config1.key, config2.key, config3.key] +        end) + +      assert length(received_configs) == 3 + +      db_keys = +        config3.value +        |> ConfigDB.from_binary() +        |> Keyword.keys() +        |> ConfigDB.convert() + +      Enum.each(received_configs, fn %{"value" => value, "db" => db} -> +        assert db in [[config1.key], [config2.key], db_keys] + +        assert value in [ +                 ConfigDB.from_binary_with_convert(config1.value), +                 ConfigDB.from_binary_with_convert(config2.value), +                 ConfigDB.from_binary_with_convert(config3.value) +               ] +      end) +    end + +    test "subkeys with full update right merge", %{conn: conn} do +      config1 = +        insert(:config, +          key: ":emoji", +          value: ConfigDB.to_binary(groups: [a: 1, b: 2], key: [a: 1]) +        ) + +      config2 = +        insert(:config, +          key: ":assets", +          value: ConfigDB.to_binary(mascots: [a: 1, b: 2], key: [a: 1]) +        ) + +      %{"configs" => configs} = +        conn +        |> get("/api/pleroma/admin/config") +        |> json_response(200) + +      vals = +        Enum.filter(configs, fn %{"group" => group, "key" => key} -> +          group == ":pleroma" and key in [config1.key, config2.key] +        end) + +      emoji = Enum.find(vals, fn %{"key" => key} -> key == ":emoji" end) +      assets = Enum.find(vals, fn %{"key" => key} -> key == ":assets" end) + +      emoji_val = ConfigDB.transform_with_out_binary(emoji["value"]) +      assets_val = ConfigDB.transform_with_out_binary(assets["value"]) + +      assert emoji_val[:groups] == [a: 1, b: 2] +      assert assets_val[:mascots] == [a: 1, b: 2] +    end    end -  describe "POST /api/pleroma/admin/config" do -    setup %{conn: conn} do -      admin = insert(:user, info: %{is_admin: true}) +  test "POST /api/pleroma/admin/config error", %{conn: conn} do +    conn = post(conn, "/api/pleroma/admin/config", %{"configs" => []}) + +    assert json_response(conn, 400) == +             "To use this endpoint you need to enable configuration from database." +  end -      temp_file = "config/test.exported_from_db.secret.exs" +  describe "POST /api/pleroma/admin/config" do +    setup do +      http = Application.get_env(:pleroma, :http)        on_exit(fn ->          Application.delete_env(:pleroma, :key1) @@ -1388,33 +2041,33 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do          Application.delete_env(:pleroma, :keyaa2)          Application.delete_env(:pleroma, Pleroma.Web.Endpoint.NotReal)          Application.delete_env(:pleroma, Pleroma.Captcha.NotReal) -        :ok = File.rm(temp_file) -      end) - -      dynamic = Pleroma.Config.get([:instance, :dynamic_configuration]) - -      Pleroma.Config.put([:instance, :dynamic_configuration], true) - -      on_exit(fn -> -        Pleroma.Config.put([:instance, :dynamic_configuration], dynamic) +        Application.put_env(:pleroma, :http, http) +        Application.put_env(:tesla, :adapter, Tesla.Mock) +        :ok = File.rm("config/test.exported_from_db.secret.exs")        end) +    end -      %{conn: assign(conn, :user, admin)} +    clear_config(:configurable_from_database) do +      Pleroma.Config.put(:configurable_from_database, true)      end +    @tag capture_log: true      test "create new config setting in db", %{conn: conn} do +      ueberauth = Application.get_env(:ueberauth, Ueberauth) +      on_exit(fn -> Application.put_env(:ueberauth, Ueberauth, ueberauth) end) +        conn =          post(conn, "/api/pleroma/admin/config", %{            configs: [ -            %{group: "pleroma", key: "key1", value: "value1"}, +            %{group: ":pleroma", key: ":key1", value: "value1"},              %{ -              group: "ueberauth", -              key: "Ueberauth.Strategy.Twitter.OAuth", +              group: ":ueberauth", +              key: "Ueberauth",                value: [%{"tuple" => [":consumer_secret", "aaaa"]}]              },              %{ -              group: "pleroma", -              key: "key2", +              group: ":pleroma", +              key: ":key2",                value: %{                  ":nested_1" => "nested_value1",                  ":nested_2" => [ @@ -1424,21 +2077,21 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do                }              },              %{ -              group: "pleroma", -              key: "key3", +              group: ":pleroma", +              key: ":key3",                value: [                  %{"nested_3" => ":nested_3", "nested_33" => "nested_33"},                  %{"nested_4" => true}                ]              },              %{ -              group: "pleroma", -              key: "key4", +              group: ":pleroma", +              key: ":key4",                value: %{":nested_5" => ":upload", "endpoint" => "https://example.com"}              },              %{ -              group: "idna", -              key: "key5", +              group: ":idna", +              key: ":key5",                value: %{"tuple" => ["string", "Pleroma.Captcha.NotReal", []]}              }            ] @@ -1447,43 +2100,49 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do        assert json_response(conn, 200) == %{                 "configs" => [                   %{ -                   "group" => "pleroma", -                   "key" => "key1", -                   "value" => "value1" +                   "group" => ":pleroma", +                   "key" => ":key1", +                   "value" => "value1", +                   "db" => [":key1"]                   },                   %{ -                   "group" => "ueberauth", -                   "key" => "Ueberauth.Strategy.Twitter.OAuth", -                   "value" => [%{"tuple" => [":consumer_secret", "aaaa"]}] +                   "group" => ":ueberauth", +                   "key" => "Ueberauth", +                   "value" => [%{"tuple" => [":consumer_secret", "aaaa"]}], +                   "db" => [":consumer_secret"]                   },                   %{ -                   "group" => "pleroma", -                   "key" => "key2", +                   "group" => ":pleroma", +                   "key" => ":key2",                     "value" => %{                       ":nested_1" => "nested_value1",                       ":nested_2" => [                         %{":nested_22" => "nested_value222"},                         %{":nested_33" => %{":nested_44" => "nested_444"}}                       ] -                   } +                   }, +                   "db" => [":key2"]                   },                   %{ -                   "group" => "pleroma", -                   "key" => "key3", +                   "group" => ":pleroma", +                   "key" => ":key3",                     "value" => [                       %{"nested_3" => ":nested_3", "nested_33" => "nested_33"},                       %{"nested_4" => true} -                   ] +                   ], +                   "db" => [":key3"]                   },                   %{ -                   "group" => "pleroma", -                   "key" => "key4", -                   "value" => %{"endpoint" => "https://example.com", ":nested_5" => ":upload"} +                   "group" => ":pleroma", +                   "key" => ":key4", +                   "value" => %{"endpoint" => "https://example.com", ":nested_5" => ":upload"}, +                   "db" => [":key4"]                   },                   %{ -                   "group" => "idna", -                   "key" => "key5", -                   "value" => %{"tuple" => ["string", "Pleroma.Captcha.NotReal", []]} +                   "group" => ":idna", +                   "key" => ":key5", +                   "value" => %{"tuple" => ["string", "Pleroma.Captcha.NotReal", []]}, +                   "db" => [":key5"]                   }                 ]               } @@ -1511,25 +2170,158 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do        assert Application.get_env(:idna, :key5) == {"string", Pleroma.Captcha.NotReal, []}      end -    test "update config setting & delete", %{conn: conn} do -      config1 = insert(:config, key: "keyaa1") -      config2 = insert(:config, key: "keyaa2") +    test "save config setting without key", %{conn: conn} do +      level = Application.get_env(:quack, :level) +      meta = Application.get_env(:quack, :meta) +      webhook_url = Application.get_env(:quack, :webhook_url) -      insert(:config, -        group: "ueberauth", -        key: "Ueberauth.Strategy.Microsoft.OAuth", -        value: :erlang.term_to_binary([]) -      ) +      on_exit(fn -> +        Application.put_env(:quack, :level, level) +        Application.put_env(:quack, :meta, meta) +        Application.put_env(:quack, :webhook_url, webhook_url) +      end)        conn =          post(conn, "/api/pleroma/admin/config", %{            configs: [ -            %{group: config1.group, key: config1.key, value: "another_value"}, -            %{group: config2.group, key: config2.key, delete: "true"},              %{ -              group: "ueberauth", -              key: "Ueberauth.Strategy.Microsoft.OAuth", -              delete: "true" +              group: ":quack", +              key: ":level", +              value: ":info" +            }, +            %{ +              group: ":quack", +              key: ":meta", +              value: [":none"] +            }, +            %{ +              group: ":quack", +              key: ":webhook_url", +              value: "https://hooks.slack.com/services/KEY" +            } +          ] +        }) + +      assert json_response(conn, 200) == %{ +               "configs" => [ +                 %{ +                   "group" => ":quack", +                   "key" => ":level", +                   "value" => ":info", +                   "db" => [":level"] +                 }, +                 %{ +                   "group" => ":quack", +                   "key" => ":meta", +                   "value" => [":none"], +                   "db" => [":meta"] +                 }, +                 %{ +                   "group" => ":quack", +                   "key" => ":webhook_url", +                   "value" => "https://hooks.slack.com/services/KEY", +                   "db" => [":webhook_url"] +                 } +               ] +             } + +      assert Application.get_env(:quack, :level) == :info +      assert Application.get_env(:quack, :meta) == [:none] +      assert Application.get_env(:quack, :webhook_url) == "https://hooks.slack.com/services/KEY" +    end + +    test "saving config with partial update", %{conn: conn} do +      config = insert(:config, key: ":key1", value: :erlang.term_to_binary(key1: 1, key2: 2)) + +      conn = +        post(conn, "/api/pleroma/admin/config", %{ +          configs: [ +            %{group: config.group, key: config.key, value: [%{"tuple" => [":key3", 3]}]} +          ] +        }) + +      assert json_response(conn, 200) == %{ +               "configs" => [ +                 %{ +                   "group" => ":pleroma", +                   "key" => ":key1", +                   "value" => [ +                     %{"tuple" => [":key1", 1]}, +                     %{"tuple" => [":key2", 2]}, +                     %{"tuple" => [":key3", 3]} +                   ], +                   "db" => [":key1", ":key2", ":key3"] +                 } +               ] +             } +    end + +    test "saving config with nested merge", %{conn: conn} do +      config = +        insert(:config, key: ":key1", value: :erlang.term_to_binary(key1: 1, key2: [k1: 1, k2: 2])) + +      conn = +        post(conn, "/api/pleroma/admin/config", %{ +          configs: [ +            %{ +              group: config.group, +              key: config.key, +              value: [ +                %{"tuple" => [":key3", 3]}, +                %{ +                  "tuple" => [ +                    ":key2", +                    [ +                      %{"tuple" => [":k2", 1]}, +                      %{"tuple" => [":k3", 3]} +                    ] +                  ] +                } +              ] +            } +          ] +        }) + +      assert json_response(conn, 200) == %{ +               "configs" => [ +                 %{ +                   "group" => ":pleroma", +                   "key" => ":key1", +                   "value" => [ +                     %{"tuple" => [":key1", 1]}, +                     %{"tuple" => [":key3", 3]}, +                     %{ +                       "tuple" => [ +                         ":key2", +                         [ +                           %{"tuple" => [":k1", 1]}, +                           %{"tuple" => [":k2", 1]}, +                           %{"tuple" => [":k3", 3]} +                         ] +                       ] +                     } +                   ], +                   "db" => [":key1", ":key3", ":key2"] +                 } +               ] +             } +    end + +    test "saving special atoms", %{conn: conn} do +      conn = +        post(conn, "/api/pleroma/admin/config", %{ +          "configs" => [ +            %{ +              "group" => ":pleroma", +              "key" => ":key1", +              "value" => [ +                %{ +                  "tuple" => [ +                    ":ssl_options", +                    [%{"tuple" => [":versions", [":tlsv1", ":tlsv1.1", ":tlsv1.2"]]}] +                  ] +                } +              ]              }            ]          }) @@ -1537,47 +2329,239 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do        assert json_response(conn, 200) == %{                 "configs" => [                   %{ -                   "group" => "pleroma", +                   "group" => ":pleroma", +                   "key" => ":key1", +                   "value" => [ +                     %{ +                       "tuple" => [ +                         ":ssl_options", +                         [%{"tuple" => [":versions", [":tlsv1", ":tlsv1.1", ":tlsv1.2"]]}] +                       ] +                     } +                   ], +                   "db" => [":ssl_options"] +                 } +               ] +             } + +      assert Application.get_env(:pleroma, :key1) == [ +               ssl_options: [versions: [:tlsv1, :"tlsv1.1", :"tlsv1.2"]] +             ] +    end + +    test "saving full setting if value is in full_key_update list", %{conn: conn} do +      backends = Application.get_env(:logger, :backends) +      on_exit(fn -> Application.put_env(:logger, :backends, backends) end) + +      config = +        insert(:config, +          group: ":logger", +          key: ":backends", +          value: :erlang.term_to_binary([]) +        ) + +      conn = +        post(conn, "/api/pleroma/admin/config", %{ +          configs: [ +            %{ +              group: config.group, +              key: config.key, +              value: [":console", %{"tuple" => ["ExSyslogger", ":ex_syslogger"]}] +            } +          ] +        }) + +      assert json_response(conn, 200) == %{ +               "configs" => [ +                 %{ +                   "group" => ":logger", +                   "key" => ":backends", +                   "value" => [ +                     ":console", +                     %{"tuple" => ["ExSyslogger", ":ex_syslogger"]} +                   ], +                   "db" => [":backends"] +                 } +               ] +             } + +      assert Application.get_env(:logger, :backends) == [ +               :console, +               {ExSyslogger, :ex_syslogger} +             ] + +      ExUnit.CaptureLog.capture_log(fn -> +        require Logger +        Logger.warn("Ooops...") +      end) =~ "Ooops..." +    end + +    test "saving full setting if value is not keyword", %{conn: conn} do +      config = +        insert(:config, +          group: ":tesla", +          key: ":adapter", +          value: :erlang.term_to_binary(Tesla.Adapter.Hackey) +        ) + +      conn = +        post(conn, "/api/pleroma/admin/config", %{ +          configs: [ +            %{group: config.group, key: config.key, value: "Tesla.Adapter.Httpc"} +          ] +        }) + +      assert json_response(conn, 200) == %{ +               "configs" => [ +                 %{ +                   "group" => ":tesla", +                   "key" => ":adapter", +                   "value" => "Tesla.Adapter.Httpc", +                   "db" => [":adapter"] +                 } +               ] +             } +    end + +    test "update config setting & delete with fallback to default value", %{ +      conn: conn, +      admin: admin, +      token: token +    } do +      ueberauth = Application.get_env(:ueberauth, Ueberauth) +      config1 = insert(:config, key: ":keyaa1") +      config2 = insert(:config, key: ":keyaa2") + +      config3 = +        insert(:config, +          group: ":ueberauth", +          key: "Ueberauth" +        ) + +      conn = +        post(conn, "/api/pleroma/admin/config", %{ +          configs: [ +            %{group: config1.group, key: config1.key, value: "another_value"}, +            %{group: config2.group, key: config2.key, value: "another_value"} +          ] +        }) + +      assert json_response(conn, 200) == %{ +               "configs" => [ +                 %{ +                   "group" => ":pleroma",                     "key" => config1.key, -                   "value" => "another_value" +                   "value" => "another_value", +                   "db" => [":keyaa1"] +                 }, +                 %{ +                   "group" => ":pleroma", +                   "key" => config2.key, +                   "value" => "another_value", +                   "db" => [":keyaa2"]                   }                 ]               }        assert Application.get_env(:pleroma, :keyaa1) == "another_value" -      refute Application.get_env(:pleroma, :keyaa2) +      assert Application.get_env(:pleroma, :keyaa2) == "another_value" +      assert Application.get_env(:ueberauth, Ueberauth) == ConfigDB.from_binary(config3.value) + +      conn = +        build_conn() +        |> assign(:user, admin) +        |> assign(:token, token) +        |> post("/api/pleroma/admin/config", %{ +          configs: [ +            %{group: config2.group, key: config2.key, delete: true}, +            %{ +              group: ":ueberauth", +              key: "Ueberauth", +              delete: true +            } +          ] +        }) + +      assert json_response(conn, 200) == %{ +               "configs" => [] +             } + +      assert Application.get_env(:ueberauth, Ueberauth) == ueberauth +      refute Keyword.has_key?(Application.get_all_env(:pleroma), :keyaa2)      end      test "common config example", %{conn: conn} do +      adapter = Application.get_env(:tesla, :adapter) +      on_exit(fn -> Application.put_env(:tesla, :adapter, adapter) end) +        conn =          post(conn, "/api/pleroma/admin/config", %{            configs: [              %{ -              "group" => "pleroma", +              "group" => ":pleroma",                "key" => "Pleroma.Captcha.NotReal",                "value" => [                  %{"tuple" => [":enabled", false]},                  %{"tuple" => [":method", "Pleroma.Captcha.Kocaptcha"]},                  %{"tuple" => [":seconds_valid", 60]},                  %{"tuple" => [":path", ""]}, -                %{"tuple" => [":key1", nil]} +                %{"tuple" => [":key1", nil]}, +                %{"tuple" => [":partial_chain", "&:hackney_connect.partial_chain/1"]}, +                %{"tuple" => [":regex1", "~r/https:\/\/example.com/"]}, +                %{"tuple" => [":regex2", "~r/https:\/\/example.com/u"]}, +                %{"tuple" => [":regex3", "~r/https:\/\/example.com/i"]}, +                %{"tuple" => [":regex4", "~r/https:\/\/example.com/s"]}, +                %{"tuple" => [":name", "Pleroma"]}                ] +            }, +            %{ +              "group" => ":tesla", +              "key" => ":adapter", +              "value" => "Tesla.Adapter.Httpc"              }            ]          }) +      assert Application.get_env(:tesla, :adapter) == Tesla.Adapter.Httpc +      assert Pleroma.Config.get([Pleroma.Captcha.NotReal, :name]) == "Pleroma" +        assert json_response(conn, 200) == %{                 "configs" => [                   %{ -                   "group" => "pleroma", +                   "group" => ":pleroma",                     "key" => "Pleroma.Captcha.NotReal",                     "value" => [                       %{"tuple" => [":enabled", false]},                       %{"tuple" => [":method", "Pleroma.Captcha.Kocaptcha"]},                       %{"tuple" => [":seconds_valid", 60]},                       %{"tuple" => [":path", ""]}, -                     %{"tuple" => [":key1", nil]} +                     %{"tuple" => [":key1", nil]}, +                     %{"tuple" => [":partial_chain", "&:hackney_connect.partial_chain/1"]}, +                     %{"tuple" => [":regex1", "~r/https:\\/\\/example.com/"]}, +                     %{"tuple" => [":regex2", "~r/https:\\/\\/example.com/u"]}, +                     %{"tuple" => [":regex3", "~r/https:\\/\\/example.com/i"]}, +                     %{"tuple" => [":regex4", "~r/https:\\/\\/example.com/s"]}, +                     %{"tuple" => [":name", "Pleroma"]} +                   ], +                   "db" => [ +                     ":enabled", +                     ":method", +                     ":seconds_valid", +                     ":path", +                     ":key1", +                     ":partial_chain", +                     ":regex1", +                     ":regex2", +                     ":regex3", +                     ":regex4", +                     ":name"                     ] +                 }, +                 %{ +                   "group" => ":tesla", +                   "key" => ":adapter", +                   "value" => "Tesla.Adapter.Httpc", +                   "db" => [":adapter"]                   }                 ]               } @@ -1588,7 +2572,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do          post(conn, "/api/pleroma/admin/config", %{            configs: [              %{ -              "group" => "pleroma", +              "group" => ":pleroma",                "key" => "Pleroma.Web.Endpoint.NotReal",                "value" => [                  %{ @@ -1652,7 +2636,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do        assert json_response(conn, 200) == %{                 "configs" => [                   %{ -                   "group" => "pleroma", +                   "group" => ":pleroma",                     "key" => "Pleroma.Web.Endpoint.NotReal",                     "value" => [                       %{ @@ -1708,7 +2692,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do                           ]                         ]                       } -                   ] +                   ], +                   "db" => [":http"]                   }                 ]               } @@ -1719,7 +2704,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do          post(conn, "/api/pleroma/admin/config", %{            configs: [              %{ -              "group" => "pleroma", +              "group" => ":pleroma",                "key" => ":key1",                "value" => [                  %{"tuple" => [":key2", "some_val"]}, @@ -1749,7 +2734,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do                 %{                   "configs" => [                     %{ -                     "group" => "pleroma", +                     "group" => ":pleroma",                       "key" => ":key1",                       "value" => [                         %{"tuple" => [":key2", "some_val"]}, @@ -1770,7 +2755,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do                             }                           ]                         } -                     ] +                     ], +                     "db" => [":key2", ":key3"]                     }                   ]                 } @@ -1781,7 +2767,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do          post(conn, "/api/pleroma/admin/config", %{            configs: [              %{ -              "group" => "pleroma", +              "group" => ":pleroma",                "key" => ":key1",                "value" => %{"key" => "some_val"}              } @@ -1792,92 +2778,104 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do                 %{                   "configs" => [                     %{ -                     "group" => "pleroma", +                     "group" => ":pleroma",                       "key" => ":key1", -                     "value" => %{"key" => "some_val"} +                     "value" => %{"key" => "some_val"}, +                     "db" => [":key1"]                     }                   ]                 }      end -    test "dispatch setting", %{conn: conn} do +    test "queues key as atom", %{conn: conn} do        conn =          post(conn, "/api/pleroma/admin/config", %{            configs: [              %{ -              "group" => "pleroma", -              "key" => "Pleroma.Web.Endpoint.NotReal", +              "group" => ":oban", +              "key" => ":queues",                "value" => [ -                %{ -                  "tuple" => [ -                    ":http", -                    [ -                      %{"tuple" => [":ip", %{"tuple" => [127, 0, 0, 1]}]}, -                      %{"tuple" => [":dispatch", ["{:_, -       [ -         {\"/api/v1/streaming\", Pleroma.Web.MastodonAPI.WebsocketHandler, []}, -         {\"/websocket\", Phoenix.Endpoint.CowboyWebSocket, -          {Phoenix.Transports.WebSocket, -           {Pleroma.Web.Endpoint, Pleroma.Web.UserSocket, [path: \"/websocket\"]}}}, -         {:_, Phoenix.Endpoint.Cowboy2Handler, {Pleroma.Web.Endpoint, []}} -       ]}"]]} -                    ] -                  ] -                } +                %{"tuple" => [":federator_incoming", 50]}, +                %{"tuple" => [":federator_outgoing", 50]}, +                %{"tuple" => [":web_push", 50]}, +                %{"tuple" => [":mailer", 10]}, +                %{"tuple" => [":transmogrifier", 20]}, +                %{"tuple" => [":scheduled_activities", 10]}, +                %{"tuple" => [":background", 5]}                ]              }            ]          }) -      dispatch_string = -        "{:_, [{\"/api/v1/streaming\", Pleroma.Web.MastodonAPI.WebsocketHandler, []}, " <> -          "{\"/websocket\", Phoenix.Endpoint.CowboyWebSocket, {Phoenix.Transports.WebSocket, " <> -          "{Pleroma.Web.Endpoint, Pleroma.Web.UserSocket, [path: \"/websocket\"]}}}, " <> -          "{:_, Phoenix.Endpoint.Cowboy2Handler, {Pleroma.Web.Endpoint, []}}]}" -        assert json_response(conn, 200) == %{                 "configs" => [                   %{ -                   "group" => "pleroma", -                   "key" => "Pleroma.Web.Endpoint.NotReal", +                   "group" => ":oban", +                   "key" => ":queues",                     "value" => [ -                     %{ -                       "tuple" => [ -                         ":http", -                         [ -                           %{"tuple" => [":ip", %{"tuple" => [127, 0, 0, 1]}]}, -                           %{ -                             "tuple" => [ -                               ":dispatch", -                               [ -                                 dispatch_string -                               ] -                             ] -                           } -                         ] -                       ] -                     } +                     %{"tuple" => [":federator_incoming", 50]}, +                     %{"tuple" => [":federator_outgoing", 50]}, +                     %{"tuple" => [":web_push", 50]}, +                     %{"tuple" => [":mailer", 10]}, +                     %{"tuple" => [":transmogrifier", 20]}, +                     %{"tuple" => [":scheduled_activities", 10]}, +                     %{"tuple" => [":background", 5]} +                   ], +                   "db" => [ +                     ":federator_incoming", +                     ":federator_outgoing", +                     ":web_push", +                     ":mailer", +                     ":transmogrifier", +                     ":scheduled_activities", +                     ":background"                     ]                   }                 ]               }      end -    test "queues key as atom", %{conn: conn} do +    test "delete part of settings by atom subkeys", %{conn: conn} do +      config = +        insert(:config, +          key: ":keyaa1", +          value: :erlang.term_to_binary(subkey1: "val1", subkey2: "val2", subkey3: "val3") +        ) +        conn =          post(conn, "/api/pleroma/admin/config", %{            configs: [              %{ -              "group" => "pleroma_job_queue", -              "key" => ":queues", -              "value" => [ -                %{"tuple" => [":federator_incoming", 50]}, -                %{"tuple" => [":federator_outgoing", 50]}, -                %{"tuple" => [":web_push", 50]}, -                %{"tuple" => [":mailer", 10]}, -                %{"tuple" => [":transmogrifier", 20]}, -                %{"tuple" => [":scheduled_activities", 10]}, -                %{"tuple" => [":background", 5]} +              group: config.group, +              key: config.key, +              subkeys: [":subkey1", ":subkey3"], +              delete: true +            } +          ] +        }) + +      assert json_response(conn, 200) == %{ +               "configs" => [ +                 %{ +                   "group" => ":pleroma", +                   "key" => ":keyaa1", +                   "value" => [%{"tuple" => [":subkey2", "val2"]}], +                   "db" => [":subkey2"] +                 } +               ] +             } +    end + +    test "proxy tuple localhost", %{conn: conn} do +      conn = +        post(conn, "/api/pleroma/admin/config", %{ +          configs: [ +            %{ +              group: ":pleroma", +              key: ":http", +              value: [ +                %{"tuple" => [":proxy_url", %{"tuple" => [":socks5", "localhost", 1234]}]}, +                %{"tuple" => [":send_user_agent", false]}                ]              }            ] @@ -1886,21 +2884,602 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do        assert json_response(conn, 200) == %{                 "configs" => [                   %{ -                   "group" => "pleroma_job_queue", -                   "key" => ":queues", +                   "group" => ":pleroma", +                   "key" => ":http",                     "value" => [ -                     %{"tuple" => [":federator_incoming", 50]}, -                     %{"tuple" => [":federator_outgoing", 50]}, -                     %{"tuple" => [":web_push", 50]}, -                     %{"tuple" => [":mailer", 10]}, -                     %{"tuple" => [":transmogrifier", 20]}, -                     %{"tuple" => [":scheduled_activities", 10]}, -                     %{"tuple" => [":background", 5]} -                   ] +                     %{"tuple" => [":proxy_url", %{"tuple" => [":socks5", "localhost", 1234]}]}, +                     %{"tuple" => [":send_user_agent", false]} +                   ], +                   "db" => [":proxy_url", ":send_user_agent"]                   }                 ]               }      end + +    test "proxy tuple domain", %{conn: conn} do +      conn = +        post(conn, "/api/pleroma/admin/config", %{ +          configs: [ +            %{ +              group: ":pleroma", +              key: ":http", +              value: [ +                %{"tuple" => [":proxy_url", %{"tuple" => [":socks5", "domain.com", 1234]}]}, +                %{"tuple" => [":send_user_agent", false]} +              ] +            } +          ] +        }) + +      assert json_response(conn, 200) == %{ +               "configs" => [ +                 %{ +                   "group" => ":pleroma", +                   "key" => ":http", +                   "value" => [ +                     %{"tuple" => [":proxy_url", %{"tuple" => [":socks5", "domain.com", 1234]}]}, +                     %{"tuple" => [":send_user_agent", false]} +                   ], +                   "db" => [":proxy_url", ":send_user_agent"] +                 } +               ] +             } +    end + +    test "proxy tuple ip", %{conn: conn} do +      conn = +        post(conn, "/api/pleroma/admin/config", %{ +          configs: [ +            %{ +              group: ":pleroma", +              key: ":http", +              value: [ +                %{"tuple" => [":proxy_url", %{"tuple" => [":socks5", "127.0.0.1", 1234]}]}, +                %{"tuple" => [":send_user_agent", false]} +              ] +            } +          ] +        }) + +      assert json_response(conn, 200) == %{ +               "configs" => [ +                 %{ +                   "group" => ":pleroma", +                   "key" => ":http", +                   "value" => [ +                     %{"tuple" => [":proxy_url", %{"tuple" => [":socks5", "127.0.0.1", 1234]}]}, +                     %{"tuple" => [":send_user_agent", false]} +                   ], +                   "db" => [":proxy_url", ":send_user_agent"] +                 } +               ] +             } +    end +  end + +  describe "config mix tasks run" do +    setup do +      Mix.shell(Mix.Shell.Quiet) + +      on_exit(fn -> +        Mix.shell(Mix.Shell.IO) +      end) + +      :ok +    end + +    clear_config(:configurable_from_database) do +      Pleroma.Config.put(:configurable_from_database, true) +    end + +    clear_config([:feed, :post_title]) do +      Pleroma.Config.put([:feed, :post_title], %{max_length: 100, omission: "…"}) +    end + +    test "transfer settings to DB and to file", %{conn: conn} do +      assert Repo.all(Pleroma.ConfigDB) == [] +      Mix.Tasks.Pleroma.Config.migrate_to_db("test/fixtures/config/temp.secret.exs") +      assert Repo.aggregate(Pleroma.ConfigDB, :count, :id) > 0 + +      conn = get(conn, "/api/pleroma/admin/config/migrate_from_db") + +      assert json_response(conn, 200) == %{} +      assert Repo.all(Pleroma.ConfigDB) == [] +    end + +    test "returns error if configuration from database is off", %{conn: conn} do +      initial = Pleroma.Config.get(:configurable_from_database) +      on_exit(fn -> Pleroma.Config.put(:configurable_from_database, initial) end) +      Pleroma.Config.put(:configurable_from_database, false) + +      conn = get(conn, "/api/pleroma/admin/config/migrate_from_db") + +      assert json_response(conn, 400) == +               "To use this endpoint you need to enable configuration from database." + +      assert Repo.all(Pleroma.ConfigDB) == [] +    end +  end + +  describe "GET /api/pleroma/admin/users/:nickname/statuses" do +    setup do +      user = insert(:user) + +      date1 = (DateTime.to_unix(DateTime.utc_now()) + 2000) |> DateTime.from_unix!() +      date2 = (DateTime.to_unix(DateTime.utc_now()) + 1000) |> DateTime.from_unix!() +      date3 = (DateTime.to_unix(DateTime.utc_now()) + 3000) |> DateTime.from_unix!() + +      insert(:note_activity, user: user, published: date1) +      insert(:note_activity, user: user, published: date2) +      insert(:note_activity, user: user, published: date3) + +      %{user: user} +    end + +    test "renders user's statuses", %{conn: conn, user: user} do +      conn = get(conn, "/api/pleroma/admin/users/#{user.nickname}/statuses") + +      assert json_response(conn, 200) |> length() == 3 +    end + +    test "renders user's statuses with a limit", %{conn: conn, user: user} do +      conn = get(conn, "/api/pleroma/admin/users/#{user.nickname}/statuses?page_size=2") + +      assert json_response(conn, 200) |> length() == 2 +    end + +    test "doesn't return private statuses by default", %{conn: conn, user: user} do +      {:ok, _private_status} = +        CommonAPI.post(user, %{"status" => "private", "visibility" => "private"}) + +      {:ok, _public_status} = +        CommonAPI.post(user, %{"status" => "public", "visibility" => "public"}) + +      conn = get(conn, "/api/pleroma/admin/users/#{user.nickname}/statuses") + +      assert json_response(conn, 200) |> length() == 4 +    end + +    test "returns private statuses with godmode on", %{conn: conn, user: user} do +      {:ok, _private_status} = +        CommonAPI.post(user, %{"status" => "private", "visibility" => "private"}) + +      {:ok, _public_status} = +        CommonAPI.post(user, %{"status" => "public", "visibility" => "public"}) + +      conn = get(conn, "/api/pleroma/admin/users/#{user.nickname}/statuses?godmode=true") + +      assert json_response(conn, 200) |> length() == 5 +    end +  end + +  describe "GET /api/pleroma/admin/moderation_log" do +    setup do +      moderator = insert(:user, is_moderator: true) + +      %{moderator: moderator} +    end + +    test "returns the log", %{conn: conn, admin: admin} do +      Repo.insert(%ModerationLog{ +        data: %{ +          actor: %{ +            "id" => admin.id, +            "nickname" => admin.nickname, +            "type" => "user" +          }, +          action: "relay_follow", +          target: "https://example.org/relay" +        }, +        inserted_at: NaiveDateTime.truncate(~N[2017-08-15 15:47:06.597036], :second) +      }) + +      Repo.insert(%ModerationLog{ +        data: %{ +          actor: %{ +            "id" => admin.id, +            "nickname" => admin.nickname, +            "type" => "user" +          }, +          action: "relay_unfollow", +          target: "https://example.org/relay" +        }, +        inserted_at: NaiveDateTime.truncate(~N[2017-08-16 15:47:06.597036], :second) +      }) + +      conn = get(conn, "/api/pleroma/admin/moderation_log") + +      response = json_response(conn, 200) +      [first_entry, second_entry] = response["items"] + +      assert response["total"] == 2 +      assert first_entry["data"]["action"] == "relay_unfollow" + +      assert first_entry["message"] == +               "@#{admin.nickname} unfollowed relay: https://example.org/relay" + +      assert second_entry["data"]["action"] == "relay_follow" + +      assert second_entry["message"] == +               "@#{admin.nickname} followed relay: https://example.org/relay" +    end + +    test "returns the log with pagination", %{conn: conn, admin: admin} do +      Repo.insert(%ModerationLog{ +        data: %{ +          actor: %{ +            "id" => admin.id, +            "nickname" => admin.nickname, +            "type" => "user" +          }, +          action: "relay_follow", +          target: "https://example.org/relay" +        }, +        inserted_at: NaiveDateTime.truncate(~N[2017-08-15 15:47:06.597036], :second) +      }) + +      Repo.insert(%ModerationLog{ +        data: %{ +          actor: %{ +            "id" => admin.id, +            "nickname" => admin.nickname, +            "type" => "user" +          }, +          action: "relay_unfollow", +          target: "https://example.org/relay" +        }, +        inserted_at: NaiveDateTime.truncate(~N[2017-08-16 15:47:06.597036], :second) +      }) + +      conn1 = get(conn, "/api/pleroma/admin/moderation_log?page_size=1&page=1") + +      response1 = json_response(conn1, 200) +      [first_entry] = response1["items"] + +      assert response1["total"] == 2 +      assert response1["items"] |> length() == 1 +      assert first_entry["data"]["action"] == "relay_unfollow" + +      assert first_entry["message"] == +               "@#{admin.nickname} unfollowed relay: https://example.org/relay" + +      conn2 = get(conn, "/api/pleroma/admin/moderation_log?page_size=1&page=2") + +      response2 = json_response(conn2, 200) +      [second_entry] = response2["items"] + +      assert response2["total"] == 2 +      assert response2["items"] |> length() == 1 +      assert second_entry["data"]["action"] == "relay_follow" + +      assert second_entry["message"] == +               "@#{admin.nickname} followed relay: https://example.org/relay" +    end + +    test "filters log by date", %{conn: conn, admin: admin} do +      first_date = "2017-08-15T15:47:06Z" +      second_date = "2017-08-20T15:47:06Z" + +      Repo.insert(%ModerationLog{ +        data: %{ +          actor: %{ +            "id" => admin.id, +            "nickname" => admin.nickname, +            "type" => "user" +          }, +          action: "relay_follow", +          target: "https://example.org/relay" +        }, +        inserted_at: NaiveDateTime.from_iso8601!(first_date) +      }) + +      Repo.insert(%ModerationLog{ +        data: %{ +          actor: %{ +            "id" => admin.id, +            "nickname" => admin.nickname, +            "type" => "user" +          }, +          action: "relay_unfollow", +          target: "https://example.org/relay" +        }, +        inserted_at: NaiveDateTime.from_iso8601!(second_date) +      }) + +      conn1 = +        get( +          conn, +          "/api/pleroma/admin/moderation_log?start_date=#{second_date}" +        ) + +      response1 = json_response(conn1, 200) +      [first_entry] = response1["items"] + +      assert response1["total"] == 1 +      assert first_entry["data"]["action"] == "relay_unfollow" + +      assert first_entry["message"] == +               "@#{admin.nickname} unfollowed relay: https://example.org/relay" +    end + +    test "returns log filtered by user", %{conn: conn, admin: admin, moderator: moderator} do +      Repo.insert(%ModerationLog{ +        data: %{ +          actor: %{ +            "id" => admin.id, +            "nickname" => admin.nickname, +            "type" => "user" +          }, +          action: "relay_follow", +          target: "https://example.org/relay" +        } +      }) + +      Repo.insert(%ModerationLog{ +        data: %{ +          actor: %{ +            "id" => moderator.id, +            "nickname" => moderator.nickname, +            "type" => "user" +          }, +          action: "relay_unfollow", +          target: "https://example.org/relay" +        } +      }) + +      conn1 = get(conn, "/api/pleroma/admin/moderation_log?user_id=#{moderator.id}") + +      response1 = json_response(conn1, 200) +      [first_entry] = response1["items"] + +      assert response1["total"] == 1 +      assert get_in(first_entry, ["data", "actor", "id"]) == moderator.id +    end + +    test "returns log filtered by search", %{conn: conn, moderator: moderator} do +      ModerationLog.insert_log(%{ +        actor: moderator, +        action: "relay_follow", +        target: "https://example.org/relay" +      }) + +      ModerationLog.insert_log(%{ +        actor: moderator, +        action: "relay_unfollow", +        target: "https://example.org/relay" +      }) + +      conn1 = get(conn, "/api/pleroma/admin/moderation_log?search=unfo") + +      response1 = json_response(conn1, 200) +      [first_entry] = response1["items"] + +      assert response1["total"] == 1 + +      assert get_in(first_entry, ["data", "message"]) == +               "@#{moderator.nickname} unfollowed relay: https://example.org/relay" +    end +  end + +  describe "PATCH /users/:nickname/force_password_reset" do +    test "sets password_reset_pending to true", %{conn: conn} do +      user = insert(:user) +      assert user.password_reset_pending == false + +      conn = +        patch(conn, "/api/pleroma/admin/users/force_password_reset", %{nicknames: [user.nickname]}) + +      assert json_response(conn, 204) == "" + +      ObanHelpers.perform_all() + +      assert User.get_by_id(user.id).password_reset_pending == true +    end +  end + +  describe "relays" do +    test "POST /relay", %{conn: conn, admin: admin} do +      conn = +        post(conn, "/api/pleroma/admin/relay", %{ +          relay_url: "http://mastodon.example.org/users/admin" +        }) + +      assert json_response(conn, 200) == "http://mastodon.example.org/users/admin" + +      log_entry = Repo.one(ModerationLog) + +      assert ModerationLog.get_log_entry_message(log_entry) == +               "@#{admin.nickname} followed relay: http://mastodon.example.org/users/admin" +    end + +    test "GET /relay", %{conn: conn} do +      relay_user = Pleroma.Web.ActivityPub.Relay.get_actor() + +      ["http://mastodon.example.org/users/admin", "https://mstdn.io/users/mayuutann"] +      |> Enum.each(fn ap_id -> +        {:ok, user} = User.get_or_fetch_by_ap_id(ap_id) +        User.follow(relay_user, user) +      end) + +      conn = get(conn, "/api/pleroma/admin/relay") + +      assert json_response(conn, 200)["relays"] -- ["mastodon.example.org", "mstdn.io"] == [] +    end + +    test "DELETE /relay", %{conn: conn, admin: admin} do +      post(conn, "/api/pleroma/admin/relay", %{ +        relay_url: "http://mastodon.example.org/users/admin" +      }) + +      conn = +        delete(conn, "/api/pleroma/admin/relay", %{ +          relay_url: "http://mastodon.example.org/users/admin" +        }) + +      assert json_response(conn, 200) == "http://mastodon.example.org/users/admin" + +      [log_entry_one, log_entry_two] = Repo.all(ModerationLog) + +      assert ModerationLog.get_log_entry_message(log_entry_one) == +               "@#{admin.nickname} followed relay: http://mastodon.example.org/users/admin" + +      assert ModerationLog.get_log_entry_message(log_entry_two) == +               "@#{admin.nickname} unfollowed relay: http://mastodon.example.org/users/admin" +    end +  end + +  describe "instances" do +    test "GET /instances/:instance/statuses", %{conn: conn} do +      user = insert(:user, local: false, nickname: "archaeme@archae.me") +      user2 = insert(:user, local: false, nickname: "test@test.com") +      insert_pair(:note_activity, user: user) +      insert(:note_activity, user: user2) + +      ret_conn = get(conn, "/api/pleroma/admin/instances/archae.me/statuses") + +      response = json_response(ret_conn, 200) + +      assert length(response) == 2 + +      ret_conn = get(conn, "/api/pleroma/admin/instances/test.com/statuses") + +      response = json_response(ret_conn, 200) + +      assert length(response) == 1 + +      ret_conn = get(conn, "/api/pleroma/admin/instances/nonexistent.com/statuses") + +      response = json_response(ret_conn, 200) + +      assert Enum.empty?(response) +    end +  end + +  describe "PATCH /confirm_email" do +    test "it confirms emails of two users", %{conn: conn, admin: admin} do +      [first_user, second_user] = insert_pair(:user, confirmation_pending: true) + +      assert first_user.confirmation_pending == true +      assert second_user.confirmation_pending == true + +      ret_conn = +        patch(conn, "/api/pleroma/admin/users/confirm_email", %{ +          nicknames: [ +            first_user.nickname, +            second_user.nickname +          ] +        }) + +      assert ret_conn.status == 200 + +      assert first_user.confirmation_pending == true +      assert second_user.confirmation_pending == true + +      log_entry = Repo.one(ModerationLog) + +      assert ModerationLog.get_log_entry_message(log_entry) == +               "@#{admin.nickname} confirmed email for users: @#{first_user.nickname}, @#{ +                 second_user.nickname +               }" +    end +  end + +  describe "PATCH /resend_confirmation_email" do +    test "it resend emails for two users", %{conn: conn, admin: admin} do +      [first_user, second_user] = insert_pair(:user, confirmation_pending: true) + +      ret_conn = +        patch(conn, "/api/pleroma/admin/users/resend_confirmation_email", %{ +          nicknames: [ +            first_user.nickname, +            second_user.nickname +          ] +        }) + +      assert ret_conn.status == 200 + +      log_entry = Repo.one(ModerationLog) + +      assert ModerationLog.get_log_entry_message(log_entry) == +               "@#{admin.nickname} re-sent confirmation email for users: @#{first_user.nickname}, @#{ +                 second_user.nickname +               }" +    end +  end + +  describe "POST /reports/:id/notes" do +    setup %{conn: conn, admin: admin} do +      [reporter, target_user] = insert_pair(:user) +      activity = insert(:note_activity, user: target_user) + +      {:ok, %{id: report_id}} = +        CommonAPI.report(reporter, %{ +          "account_id" => target_user.id, +          "comment" => "I feel offended", +          "status_ids" => [activity.id] +        }) + +      post(conn, "/api/pleroma/admin/reports/#{report_id}/notes", %{ +        content: "this is disgusting!" +      }) + +      post(conn, "/api/pleroma/admin/reports/#{report_id}/notes", %{ +        content: "this is disgusting2!" +      }) + +      %{ +        admin_id: admin.id, +        report_id: report_id +      } +    end + +    test "it creates report note", %{admin_id: admin_id, report_id: report_id} do +      [note, _] = Repo.all(ReportNote) + +      assert %{ +               activity_id: ^report_id, +               content: "this is disgusting!", +               user_id: ^admin_id +             } = note +    end + +    test "it returns reports with notes", %{conn: conn, admin: admin} do +      conn = get(conn, "/api/pleroma/admin/reports") + +      response = json_response(conn, 200) +      notes = hd(response["reports"])["notes"] +      [note, _] = notes + +      assert note["user"]["nickname"] == admin.nickname +      assert note["content"] == "this is disgusting!" +      assert note["created_at"] +      assert response["total"] == 1 +    end + +    test "it deletes the note", %{conn: conn, report_id: report_id} do +      assert ReportNote |> Repo.all() |> length() == 2 + +      [note, _] = Repo.all(ReportNote) + +      delete(conn, "/api/pleroma/admin/reports/#{report_id}/notes/#{note.id}") + +      assert ReportNote |> Repo.all() |> length() == 1 +    end +  end + +  test "GET /api/pleroma/admin/config/descriptions", %{conn: conn} do +    admin = insert(:user, is_admin: true) + +    conn = +      assign(conn, :user, admin) +      |> get("/api/pleroma/admin/config/descriptions") + +    assert [child | _others] = json_response(conn, 200) + +    assert child["children"] +    assert child["key"] +    assert String.starts_with?(child["group"], ":") +    assert child["description"]    end  end diff --git a/test/web/admin_api/config_test.exs b/test/web/admin_api/config_test.exs deleted file mode 100644 index d41666ef3..000000000 --- a/test/web/admin_api/config_test.exs +++ /dev/null @@ -1,465 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Web.AdminAPI.ConfigTest do -  use Pleroma.DataCase, async: true -  import Pleroma.Factory -  alias Pleroma.Web.AdminAPI.Config - -  test "get_by_key/1" do -    config = insert(:config) -    insert(:config) - -    assert config == Config.get_by_params(%{group: config.group, key: config.key}) -  end - -  test "create/1" do -    {:ok, config} = Config.create(%{group: "pleroma", key: "some_key", value: "some_value"}) -    assert config == Config.get_by_params(%{group: "pleroma", key: "some_key"}) -  end - -  test "update/1" do -    config = insert(:config) -    {:ok, updated} = Config.update(config, %{value: "some_value"}) -    loaded = Config.get_by_params(%{group: config.group, key: config.key}) -    assert loaded == updated -  end - -  test "update_or_create/1" do -    config = insert(:config) -    key2 = "another_key" - -    params = [ -      %{group: "pleroma", key: key2, value: "another_value"}, -      %{group: config.group, key: config.key, value: "new_value"} -    ] - -    assert Repo.all(Config) |> length() == 1 - -    Enum.each(params, &Config.update_or_create(&1)) - -    assert Repo.all(Config) |> length() == 2 - -    config1 = Config.get_by_params(%{group: config.group, key: config.key}) -    config2 = Config.get_by_params(%{group: "pleroma", key: key2}) - -    assert config1.value == Config.transform("new_value") -    assert config2.value == Config.transform("another_value") -  end - -  test "delete/1" do -    config = insert(:config) -    {:ok, _} = Config.delete(%{key: config.key, group: config.group}) -    refute Config.get_by_params(%{key: config.key, group: config.group}) -  end - -  describe "transform/1" do -    test "string" do -      binary = Config.transform("value as string") -      assert binary == :erlang.term_to_binary("value as string") -      assert Config.from_binary(binary) == "value as string" -    end - -    test "boolean" do -      binary = Config.transform(false) -      assert binary == :erlang.term_to_binary(false) -      assert Config.from_binary(binary) == false -    end - -    test "nil" do -      binary = Config.transform(nil) -      assert binary == :erlang.term_to_binary(nil) -      assert Config.from_binary(binary) == nil -    end - -    test "integer" do -      binary = Config.transform(150) -      assert binary == :erlang.term_to_binary(150) -      assert Config.from_binary(binary) == 150 -    end - -    test "atom" do -      binary = Config.transform(":atom") -      assert binary == :erlang.term_to_binary(:atom) -      assert Config.from_binary(binary) == :atom -    end - -    test "pleroma module" do -      binary = Config.transform("Pleroma.Bookmark") -      assert binary == :erlang.term_to_binary(Pleroma.Bookmark) -      assert Config.from_binary(binary) == Pleroma.Bookmark -    end - -    test "phoenix module" do -      binary = Config.transform("Phoenix.Socket.V1.JSONSerializer") -      assert binary == :erlang.term_to_binary(Phoenix.Socket.V1.JSONSerializer) -      assert Config.from_binary(binary) == Phoenix.Socket.V1.JSONSerializer -    end - -    test "sigil" do -      binary = Config.transform("~r/comp[lL][aA][iI][nN]er/") -      assert binary == :erlang.term_to_binary(~r/comp[lL][aA][iI][nN]er/) -      assert Config.from_binary(binary) == ~r/comp[lL][aA][iI][nN]er/ -    end - -    test "2 child tuple" do -      binary = Config.transform(%{"tuple" => ["v1", ":v2"]}) -      assert binary == :erlang.term_to_binary({"v1", :v2}) -      assert Config.from_binary(binary) == {"v1", :v2} -    end - -    test "tuple with n childs" do -      binary = -        Config.transform(%{ -          "tuple" => [ -            "v1", -            ":v2", -            "Pleroma.Bookmark", -            150, -            false, -            "Phoenix.Socket.V1.JSONSerializer" -          ] -        }) - -      assert binary == -               :erlang.term_to_binary( -                 {"v1", :v2, Pleroma.Bookmark, 150, false, Phoenix.Socket.V1.JSONSerializer} -               ) - -      assert Config.from_binary(binary) == -               {"v1", :v2, Pleroma.Bookmark, 150, false, Phoenix.Socket.V1.JSONSerializer} -    end - -    test "tuple with dispatch key" do -      binary = Config.transform(%{"tuple" => [":dispatch", ["{:_, -       [ -         {\"/api/v1/streaming\", Pleroma.Web.MastodonAPI.WebsocketHandler, []}, -         {\"/websocket\", Phoenix.Endpoint.CowboyWebSocket, -          {Phoenix.Transports.WebSocket, -           {Pleroma.Web.Endpoint, Pleroma.Web.UserSocket, [path: \"/websocket\"]}}}, -         {:_, Phoenix.Endpoint.Cowboy2Handler, {Pleroma.Web.Endpoint, []}} -       ]}"]]}) - -      assert binary == -               :erlang.term_to_binary( -                 {:dispatch, -                  [ -                    {:_, -                     [ -                       {"/api/v1/streaming", Pleroma.Web.MastodonAPI.WebsocketHandler, []}, -                       {"/websocket", Phoenix.Endpoint.CowboyWebSocket, -                        {Phoenix.Transports.WebSocket, -                         {Pleroma.Web.Endpoint, Pleroma.Web.UserSocket, [path: "/websocket"]}}}, -                       {:_, Phoenix.Endpoint.Cowboy2Handler, {Pleroma.Web.Endpoint, []}} -                     ]} -                  ]} -               ) - -      assert Config.from_binary(binary) == -               {:dispatch, -                [ -                  {:_, -                   [ -                     {"/api/v1/streaming", Pleroma.Web.MastodonAPI.WebsocketHandler, []}, -                     {"/websocket", Phoenix.Endpoint.CowboyWebSocket, -                      {Phoenix.Transports.WebSocket, -                       {Pleroma.Web.Endpoint, Pleroma.Web.UserSocket, [path: "/websocket"]}}}, -                     {:_, Phoenix.Endpoint.Cowboy2Handler, {Pleroma.Web.Endpoint, []}} -                   ]} -                ]} -    end - -    test "map with string key" do -      binary = Config.transform(%{"key" => "value"}) -      assert binary == :erlang.term_to_binary(%{"key" => "value"}) -      assert Config.from_binary(binary) == %{"key" => "value"} -    end - -    test "map with atom key" do -      binary = Config.transform(%{":key" => "value"}) -      assert binary == :erlang.term_to_binary(%{key: "value"}) -      assert Config.from_binary(binary) == %{key: "value"} -    end - -    test "list of strings" do -      binary = Config.transform(["v1", "v2", "v3"]) -      assert binary == :erlang.term_to_binary(["v1", "v2", "v3"]) -      assert Config.from_binary(binary) == ["v1", "v2", "v3"] -    end - -    test "list of modules" do -      binary = Config.transform(["Pleroma.Repo", "Pleroma.Activity"]) -      assert binary == :erlang.term_to_binary([Pleroma.Repo, Pleroma.Activity]) -      assert Config.from_binary(binary) == [Pleroma.Repo, Pleroma.Activity] -    end - -    test "list of atoms" do -      binary = Config.transform([":v1", ":v2", ":v3"]) -      assert binary == :erlang.term_to_binary([:v1, :v2, :v3]) -      assert Config.from_binary(binary) == [:v1, :v2, :v3] -    end - -    test "list of mixed values" do -      binary = -        Config.transform([ -          "v1", -          ":v2", -          "Pleroma.Repo", -          "Phoenix.Socket.V1.JSONSerializer", -          15, -          false -        ]) - -      assert binary == -               :erlang.term_to_binary([ -                 "v1", -                 :v2, -                 Pleroma.Repo, -                 Phoenix.Socket.V1.JSONSerializer, -                 15, -                 false -               ]) - -      assert Config.from_binary(binary) == [ -               "v1", -               :v2, -               Pleroma.Repo, -               Phoenix.Socket.V1.JSONSerializer, -               15, -               false -             ] -    end - -    test "simple keyword" do -      binary = Config.transform([%{"tuple" => [":key", "value"]}]) -      assert binary == :erlang.term_to_binary([{:key, "value"}]) -      assert Config.from_binary(binary) == [{:key, "value"}] -      assert Config.from_binary(binary) == [key: "value"] -    end - -    test "keyword" do -      binary = -        Config.transform([ -          %{"tuple" => [":types", "Pleroma.PostgresTypes"]}, -          %{"tuple" => [":telemetry_event", ["Pleroma.Repo.Instrumenter"]]}, -          %{"tuple" => [":migration_lock", nil]}, -          %{"tuple" => [":key1", 150]}, -          %{"tuple" => [":key2", "string"]} -        ]) - -      assert binary == -               :erlang.term_to_binary( -                 types: Pleroma.PostgresTypes, -                 telemetry_event: [Pleroma.Repo.Instrumenter], -                 migration_lock: nil, -                 key1: 150, -                 key2: "string" -               ) - -      assert Config.from_binary(binary) == [ -               types: Pleroma.PostgresTypes, -               telemetry_event: [Pleroma.Repo.Instrumenter], -               migration_lock: nil, -               key1: 150, -               key2: "string" -             ] -    end - -    test "complex keyword with nested mixed childs" do -      binary = -        Config.transform([ -          %{"tuple" => [":uploader", "Pleroma.Uploaders.Local"]}, -          %{"tuple" => [":filters", ["Pleroma.Upload.Filter.Dedupe"]]}, -          %{"tuple" => [":link_name", true]}, -          %{"tuple" => [":proxy_remote", false]}, -          %{"tuple" => [":common_map", %{":key" => "value"}]}, -          %{ -            "tuple" => [ -              ":proxy_opts", -              [ -                %{"tuple" => [":redirect_on_failure", false]}, -                %{"tuple" => [":max_body_length", 1_048_576]}, -                %{ -                  "tuple" => [ -                    ":http", -                    [%{"tuple" => [":follow_redirect", true]}, %{"tuple" => [":pool", ":upload"]}] -                  ] -                } -              ] -            ] -          } -        ]) - -      assert binary == -               :erlang.term_to_binary( -                 uploader: Pleroma.Uploaders.Local, -                 filters: [Pleroma.Upload.Filter.Dedupe], -                 link_name: true, -                 proxy_remote: false, -                 common_map: %{key: "value"}, -                 proxy_opts: [ -                   redirect_on_failure: false, -                   max_body_length: 1_048_576, -                   http: [ -                     follow_redirect: true, -                     pool: :upload -                   ] -                 ] -               ) - -      assert Config.from_binary(binary) == -               [ -                 uploader: Pleroma.Uploaders.Local, -                 filters: [Pleroma.Upload.Filter.Dedupe], -                 link_name: true, -                 proxy_remote: false, -                 common_map: %{key: "value"}, -                 proxy_opts: [ -                   redirect_on_failure: false, -                   max_body_length: 1_048_576, -                   http: [ -                     follow_redirect: true, -                     pool: :upload -                   ] -                 ] -               ] -    end - -    test "common keyword" do -      binary = -        Config.transform([ -          %{"tuple" => [":level", ":warn"]}, -          %{"tuple" => [":meta", [":all"]]}, -          %{"tuple" => [":path", ""]}, -          %{"tuple" => [":val", nil]}, -          %{"tuple" => [":webhook_url", "https://hooks.slack.com/services/YOUR-KEY-HERE"]} -        ]) - -      assert binary == -               :erlang.term_to_binary( -                 level: :warn, -                 meta: [:all], -                 path: "", -                 val: nil, -                 webhook_url: "https://hooks.slack.com/services/YOUR-KEY-HERE" -               ) - -      assert Config.from_binary(binary) == [ -               level: :warn, -               meta: [:all], -               path: "", -               val: nil, -               webhook_url: "https://hooks.slack.com/services/YOUR-KEY-HERE" -             ] -    end - -    test "complex keyword with sigil" do -      binary = -        Config.transform([ -          %{"tuple" => [":federated_timeline_removal", []]}, -          %{"tuple" => [":reject", ["~r/comp[lL][aA][iI][nN]er/"]]}, -          %{"tuple" => [":replace", []]} -        ]) - -      assert binary == -               :erlang.term_to_binary( -                 federated_timeline_removal: [], -                 reject: [~r/comp[lL][aA][iI][nN]er/], -                 replace: [] -               ) - -      assert Config.from_binary(binary) == -               [federated_timeline_removal: [], reject: [~r/comp[lL][aA][iI][nN]er/], replace: []] -    end - -    test "complex keyword with tuples with more than 2 values" do -      binary = -        Config.transform([ -          %{ -            "tuple" => [ -              ":http", -              [ -                %{ -                  "tuple" => [ -                    ":key1", -                    [ -                      %{ -                        "tuple" => [ -                          ":_", -                          [ -                            %{ -                              "tuple" => [ -                                "/api/v1/streaming", -                                "Pleroma.Web.MastodonAPI.WebsocketHandler", -                                [] -                              ] -                            }, -                            %{ -                              "tuple" => [ -                                "/websocket", -                                "Phoenix.Endpoint.CowboyWebSocket", -                                %{ -                                  "tuple" => [ -                                    "Phoenix.Transports.WebSocket", -                                    %{ -                                      "tuple" => [ -                                        "Pleroma.Web.Endpoint", -                                        "Pleroma.Web.UserSocket", -                                        [] -                                      ] -                                    } -                                  ] -                                } -                              ] -                            }, -                            %{ -                              "tuple" => [ -                                ":_", -                                "Phoenix.Endpoint.Cowboy2Handler", -                                %{"tuple" => ["Pleroma.Web.Endpoint", []]} -                              ] -                            } -                          ] -                        ] -                      } -                    ] -                  ] -                } -              ] -            ] -          } -        ]) - -      assert binary == -               :erlang.term_to_binary( -                 http: [ -                   key1: [ -                     _: [ -                       {"/api/v1/streaming", Pleroma.Web.MastodonAPI.WebsocketHandler, []}, -                       {"/websocket", Phoenix.Endpoint.CowboyWebSocket, -                        {Phoenix.Transports.WebSocket, -                         {Pleroma.Web.Endpoint, Pleroma.Web.UserSocket, []}}}, -                       {:_, Phoenix.Endpoint.Cowboy2Handler, {Pleroma.Web.Endpoint, []}} -                     ] -                   ] -                 ] -               ) - -      assert Config.from_binary(binary) == [ -               http: [ -                 key1: [ -                   {:_, -                    [ -                      {"/api/v1/streaming", Pleroma.Web.MastodonAPI.WebsocketHandler, []}, -                      {"/websocket", Phoenix.Endpoint.CowboyWebSocket, -                       {Phoenix.Transports.WebSocket, -                        {Pleroma.Web.Endpoint, Pleroma.Web.UserSocket, []}}}, -                      {:_, Phoenix.Endpoint.Cowboy2Handler, {Pleroma.Web.Endpoint, []}} -                    ]} -                 ] -               ] -             ] -    end -  end -end diff --git a/test/web/admin_api/search_test.exs b/test/web/admin_api/search_test.exs index 501a8d007..082e691c4 100644 --- a/test/web/admin_api/search_test.exs +++ b/test/web/admin_api/search_test.exs @@ -1,5 +1,5 @@  # Pleroma: A lightweight social networking server -# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.Web.AdminAPI.SearchTest do @@ -47,9 +47,9 @@ defmodule Pleroma.Web.AdminAPI.SearchTest do      end      test "it returns active/deactivated users" do -      insert(:user, info: %{deactivated: true}) -      insert(:user, info: %{deactivated: true}) -      insert(:user, info: %{deactivated: false}) +      insert(:user, deactivated: true) +      insert(:user, deactivated: true) +      insert(:user, deactivated: false)        {:ok, _results, active_count} =          Search.user(%{ @@ -70,7 +70,7 @@ defmodule Pleroma.Web.AdminAPI.SearchTest do      test "it returns specific user" do        insert(:user)        insert(:user) -      user = insert(:user, nickname: "bob", local: true, info: %{deactivated: false}) +      user = insert(:user, nickname: "bob", local: true, deactivated: false)        {:ok, _results, total_count} = Search.user(%{query: ""}) @@ -108,7 +108,7 @@ defmodule Pleroma.Web.AdminAPI.SearchTest do      end      test "it returns admin user" do -      admin = insert(:user, info: %{is_admin: true}) +      admin = insert(:user, is_admin: true)        insert(:user)        insert(:user) @@ -119,7 +119,7 @@ defmodule Pleroma.Web.AdminAPI.SearchTest do      end      test "it returns moderator user" do -      moderator = insert(:user, info: %{is_moderator: true}) +      moderator = insert(:user, is_moderator: true)        insert(:user)        insert(:user) diff --git a/test/web/admin_api/views/report_view_test.exs b/test/web/admin_api/views/report_view_test.exs index a00c9c579..a0c6eab3c 100644 --- a/test/web/admin_api/views/report_view_test.exs +++ b/test/web/admin_api/views/report_view_test.exs @@ -5,6 +5,7 @@  defmodule Pleroma.Web.AdminAPI.ReportViewTest do    use Pleroma.DataCase    import Pleroma.Factory +  alias Pleroma.Web.AdminAPI.Report    alias Pleroma.Web.AdminAPI.ReportView    alias Pleroma.Web.CommonAPI    alias Pleroma.Web.MastodonAPI.AccountView @@ -20,21 +21,22 @@ defmodule Pleroma.Web.AdminAPI.ReportViewTest do        content: nil,        actor:          Map.merge( -          AccountView.render("account.json", %{user: user}), +          AccountView.render("show.json", %{user: user}),            Pleroma.Web.AdminAPI.AccountView.render("show.json", %{user: user})          ),        account:          Map.merge( -          AccountView.render("account.json", %{user: other_user}), +          AccountView.render("show.json", %{user: other_user}),            Pleroma.Web.AdminAPI.AccountView.render("show.json", %{user: other_user})          ),        statuses: [], +      notes: [],        state: "open",        id: activity.id      }      result = -      ReportView.render("show.json", %{report: activity}) +      ReportView.render("show.json", Report.extract_report_info(activity))        |> Map.delete(:created_at)      assert result == expected @@ -48,25 +50,28 @@ defmodule Pleroma.Web.AdminAPI.ReportViewTest do      {:ok, report_activity} =        CommonAPI.report(user, %{"account_id" => other_user.id, "status_ids" => [activity.id]}) +    other_user = Pleroma.User.get_by_id(other_user.id) +      expected = %{        content: nil,        actor:          Map.merge( -          AccountView.render("account.json", %{user: user}), +          AccountView.render("show.json", %{user: user}),            Pleroma.Web.AdminAPI.AccountView.render("show.json", %{user: user})          ),        account:          Map.merge( -          AccountView.render("account.json", %{user: other_user}), +          AccountView.render("show.json", %{user: other_user}),            Pleroma.Web.AdminAPI.AccountView.render("show.json", %{user: other_user})          ), -      statuses: [StatusView.render("status.json", %{activity: activity})], +      statuses: [StatusView.render("show.json", %{activity: activity})],        state: "open", +      notes: [],        id: report_activity.id      }      result = -      ReportView.render("show.json", %{report: report_activity}) +      ReportView.render("show.json", Report.extract_report_info(report_activity))        |> Map.delete(:created_at)      assert result == expected @@ -78,7 +83,9 @@ defmodule Pleroma.Web.AdminAPI.ReportViewTest do      {:ok, activity} = CommonAPI.report(user, %{"account_id" => other_user.id})      {:ok, activity} = CommonAPI.update_report_state(activity.id, "closed") -    assert %{state: "closed"} = ReportView.render("show.json", %{report: activity}) + +    assert %{state: "closed"} = +             ReportView.render("show.json", Report.extract_report_info(activity))    end    test "renders report description" do @@ -92,7 +99,7 @@ defmodule Pleroma.Web.AdminAPI.ReportViewTest do        })      assert %{content: "posts are too good for this instance"} = -             ReportView.render("show.json", %{report: activity}) +             ReportView.render("show.json", Report.extract_report_info(activity))    end    test "sanitizes report description" do @@ -109,7 +116,7 @@ defmodule Pleroma.Web.AdminAPI.ReportViewTest do      activity = Map.put(activity, :data, data)      refute "<script> alert('hecked :D:D:D:D:D:D:D') </script>" == -             ReportView.render("show.json", %{report: activity})[:content] +             ReportView.render("show.json", Report.extract_report_info(activity))[:content]    end    test "doesn't error out when the user doesn't exists" do @@ -125,6 +132,6 @@ defmodule Pleroma.Web.AdminAPI.ReportViewTest do      Pleroma.User.delete(other_user)      Pleroma.User.invalidate_cache(other_user) -    assert %{} = ReportView.render("show.json", %{report: activity}) +    assert %{} = ReportView.render("show.json", Report.extract_report_info(activity))    end  end diff --git a/test/web/chat_channel_test.exs b/test/web/chat_channel_test.exs new file mode 100644 index 000000000..68c24a9f9 --- /dev/null +++ b/test/web/chat_channel_test.exs @@ -0,0 +1,37 @@ +defmodule Pleroma.Web.ChatChannelTest do +  use Pleroma.Web.ChannelCase +  alias Pleroma.Web.ChatChannel +  alias Pleroma.Web.UserSocket + +  import Pleroma.Factory + +  setup do +    user = insert(:user) + +    {:ok, _, socket} = +      socket(UserSocket, "", %{user_name: user.nickname}) +      |> subscribe_and_join(ChatChannel, "chat:public") + +    {:ok, socket: socket} +  end + +  test "it broadcasts a message", %{socket: socket} do +    push(socket, "new_msg", %{"text" => "why is tenshi eating a corndog so cute?"}) +    assert_broadcast("new_msg", %{text: "why is tenshi eating a corndog so cute?"}) +  end + +  describe "message lengths" do +    clear_config([:instance, :chat_limit]) + +    test "it ignores messages of length zero", %{socket: socket} do +      push(socket, "new_msg", %{"text" => ""}) +      refute_broadcast("new_msg", %{text: ""}) +    end + +    test "it ignores messages above a certain length", %{socket: socket} do +      Pleroma.Config.put([:instance, :chat_limit], 2) +      push(socket, "new_msg", %{"text" => "123"}) +      refute_broadcast("new_msg", %{text: "123"}) +    end +  end +end diff --git a/test/web/common_api/common_api_test.exs b/test/web/common_api/common_api_test.exs index 16b3f121d..f8963e42e 100644 --- a/test/web/common_api/common_api_test.exs +++ b/test/web/common_api/common_api_test.exs @@ -5,18 +5,69 @@  defmodule Pleroma.Web.CommonAPITest do    use Pleroma.DataCase    alias Pleroma.Activity +  alias Pleroma.Conversation.Participation    alias Pleroma.Object    alias Pleroma.User    alias Pleroma.Web.ActivityPub.ActivityPub +  alias Pleroma.Web.ActivityPub.Visibility +  alias Pleroma.Web.AdminAPI.AccountView    alias Pleroma.Web.CommonAPI    import Pleroma.Factory +  require Pleroma.Constants + +  clear_config([:instance, :safe_dm_mentions]) +  clear_config([:instance, :limit]) +  clear_config([:instance, :max_pinned_statuses]) + +  test "when replying to a conversation / participation, it will set the correct context id even if no explicit reply_to is given" do +    user = insert(:user) +    {:ok, activity} = CommonAPI.post(user, %{"status" => ".", "visibility" => "direct"}) + +    [participation] = Participation.for_user(user) + +    {:ok, convo_reply} = +      CommonAPI.post(user, %{"status" => ".", "in_reply_to_conversation_id" => participation.id}) + +    assert Visibility.is_direct?(convo_reply) + +    assert activity.data["context"] == convo_reply.data["context"] +  end + +  test "when replying to a conversation / participation, it only mentions the recipients explicitly declared in the participation" do +    har = insert(:user) +    jafnhar = insert(:user) +    tridi = insert(:user) + +    {:ok, activity} = +      CommonAPI.post(har, %{ +        "status" => "@#{jafnhar.nickname} hey", +        "visibility" => "direct" +      }) + +    assert har.ap_id in activity.recipients +    assert jafnhar.ap_id in activity.recipients + +    [participation] = Participation.for_user(har) + +    {:ok, activity} = +      CommonAPI.post(har, %{ +        "status" => "I don't really like @#{tridi.nickname}", +        "visibility" => "direct", +        "in_reply_to_status_id" => activity.id, +        "in_reply_to_conversation_id" => participation.id +      }) + +    assert har.ap_id in activity.recipients +    assert jafnhar.ap_id in activity.recipients +    refute tridi.ap_id in activity.recipients +  end +    test "with the safe_dm_mention option set, it does not mention people beyond the initial tags" do      har = insert(:user)      jafnhar = insert(:user)      tridi = insert(:user) -    option = Pleroma.Config.get([:instance, :safe_dm_mentions])      Pleroma.Config.put([:instance, :safe_dm_mentions], true)      {:ok, activity} = @@ -27,7 +78,6 @@ defmodule Pleroma.Web.CommonAPITest do      refute tridi.ap_id in activity.recipients      assert jafnhar.ap_id in activity.recipients -    Pleroma.Config.put([:instance, :safe_dm_mentions], option)    end    test "it de-duplicates tags" do @@ -49,11 +99,13 @@ defmodule Pleroma.Web.CommonAPITest do    test "it adds emoji when updating profiles" do      user = insert(:user, %{name: ":firefox:"}) -    CommonAPI.update(user) +    {:ok, activity} = CommonAPI.update(user)      user = User.get_cached_by_ap_id(user.ap_id) -    [firefox] = user.info.source_data["tag"] +    [firefox] = user.source_data["tag"]      assert firefox["name"] == ":firefox:" + +    assert Pleroma.Constants.as_public() in activity.recipients    end    describe "posting" do @@ -89,7 +141,7 @@ defmodule Pleroma.Web.CommonAPITest do        object = Object.normalize(activity) -      assert object.data["content"] == "<p><b>2hu</b></p>alert('xss')" +      assert object.data["content"] == "<p><b>2hu</b></p>alert('xss')"      end      test "it filters out obviously bad tags when accepting a post as Markdown" do @@ -105,7 +157,7 @@ defmodule Pleroma.Web.CommonAPITest do        object = Object.normalize(activity) -      assert object.data["content"] == "<p><b>2hu</b></p>alert('xss')" +      assert object.data["content"] == "<p><b>2hu</b></p>alert('xss')"      end      test "it does not allow replies to direct messages that are not direct messages themselves" do @@ -150,19 +202,58 @@ defmodule Pleroma.Web.CommonAPITest do      end      test "it returns error when character limit is exceeded" do -      limit = Pleroma.Config.get([:instance, :limit])        Pleroma.Config.put([:instance, :limit], 5)        user = insert(:user)        assert {:error, "The status is over the character limit"} =                 CommonAPI.post(user, %{"status" => "foobar"}) +    end + +    test "it can handle activities that expire" do +      user = insert(:user) -      Pleroma.Config.put([:instance, :limit], limit) +      expires_at = +        NaiveDateTime.utc_now() +        |> NaiveDateTime.truncate(:second) +        |> NaiveDateTime.add(1_000_000, :second) + +      assert {:ok, activity} = +               CommonAPI.post(user, %{"status" => "chai", "expires_in" => 1_000_000}) + +      assert expiration = Pleroma.ActivityExpiration.get_by_activity_id(activity.id) +      assert expiration.scheduled_at == expires_at      end    end    describe "reactions" do +    test "reacting to a status with an emoji" do +      user = insert(:user) +      other_user = insert(:user) + +      {:ok, activity} = CommonAPI.post(other_user, %{"status" => "cofe"}) + +      {:ok, reaction, _} = CommonAPI.react_with_emoji(activity.id, user, "👍") + +      assert reaction.data["actor"] == user.ap_id +      assert reaction.data["content"] == "👍" + +      # TODO: test error case. +    end + +    test "unreacting to a status with an emoji" do +      user = insert(:user) +      other_user = insert(:user) + +      {:ok, activity} = CommonAPI.post(other_user, %{"status" => "cofe"}) +      {:ok, reaction, _} = CommonAPI.react_with_emoji(activity.id, user, "👍") + +      {:ok, unreaction, _} = CommonAPI.unreact_with_emoji(activity.id, user, "👍") + +      assert unreaction.data["type"] == "Undo" +      assert unreaction.data["object"] == reaction.data["id"] +    end +      test "repeating a status" do        user = insert(:user)        other_user = insert(:user) @@ -172,6 +263,18 @@ defmodule Pleroma.Web.CommonAPITest do        {:ok, %Activity{}, _} = CommonAPI.repeat(activity.id, user)      end +    test "repeating a status privately" do +      user = insert(:user) +      other_user = insert(:user) + +      {:ok, activity} = CommonAPI.post(other_user, %{"status" => "cofe"}) + +      {:ok, %Activity{} = announce_activity, _} = +        CommonAPI.repeat(activity.id, user, %{"visibility" => "private"}) + +      assert Visibility.is_private?(announce_activity) +    end +      test "favoriting a status" do        user = insert(:user)        other_user = insert(:user) @@ -181,22 +284,22 @@ defmodule Pleroma.Web.CommonAPITest do        {:ok, %Activity{}, _} = CommonAPI.favorite(activity.id, user)      end -    test "retweeting a status twice returns an error" do +    test "retweeting a status twice returns the status" do        user = insert(:user)        other_user = insert(:user)        {:ok, activity} = CommonAPI.post(other_user, %{"status" => "cofe"}) -      {:ok, %Activity{}, _object} = CommonAPI.repeat(activity.id, user) -      {:error, _} = CommonAPI.repeat(activity.id, user) +      {:ok, %Activity{} = activity, object} = CommonAPI.repeat(activity.id, user) +      {:ok, ^activity, ^object} = CommonAPI.repeat(activity.id, user)      end -    test "favoriting a status twice returns an error" do +    test "favoriting a status twice returns the status" do        user = insert(:user)        other_user = insert(:user)        {:ok, activity} = CommonAPI.post(other_user, %{"status" => "cofe"}) -      {:ok, %Activity{}, _object} = CommonAPI.favorite(activity.id, user) -      {:error, _} = CommonAPI.favorite(activity.id, user) +      {:ok, %Activity{} = activity, object} = CommonAPI.favorite(activity.id, user) +      {:ok, ^activity, ^object} = CommonAPI.favorite(activity.id, user)      end    end @@ -216,7 +319,7 @@ defmodule Pleroma.Web.CommonAPITest do        id = activity.id        user = refresh_record(user) -      assert %User{info: %{pinned_activities: [^id]}} = user +      assert %User{pinned_activities: [^id]} = user      end      test "unlisted statuses can be pinned", %{user: user} do @@ -250,7 +353,7 @@ defmodule Pleroma.Web.CommonAPITest do        user = refresh_record(user) -      assert %User{info: %{pinned_activities: []}} = user +      assert %User{pinned_activities: []} = user      end      test "should unpin when deleting a status", %{user: user, activity: activity} do @@ -262,7 +365,7 @@ defmodule Pleroma.Web.CommonAPITest do        user = refresh_record(user) -      assert %User{info: %{pinned_activities: []}} = user +      assert %User{pinned_activities: []} = user      end    end @@ -310,6 +413,14 @@ defmodule Pleroma.Web.CommonAPITest do          "status_ids" => [activity.id]        } +      note_obj = %{ +        "type" => "Note", +        "id" => activity_ap_id, +        "content" => "foobar", +        "published" => activity.object.data["published"], +        "actor" => AccountView.render("show.json", %{user: target_user}) +      } +        assert {:ok, flag_activity} = CommonAPI.report(reporter, report_data)        assert %Activity{ @@ -317,7 +428,7 @@ defmodule Pleroma.Web.CommonAPITest do                 data: %{                   "type" => "Flag",                   "content" => ^comment, -                 "object" => [^target_ap_id, ^activity_ap_id], +                 "object" => [^target_ap_id, ^note_obj],                   "state" => "open"                 }               } = flag_activity @@ -337,6 +448,11 @@ defmodule Pleroma.Web.CommonAPITest do        {:ok, report} = CommonAPI.update_report_state(report_id, "resolved")        assert report.data["state"] == "resolved" + +      [reported_user, activity_id] = report.data["object"] + +      assert reported_user == target_user.ap_id +      assert activity_id == activity.data["id"]      end      test "does not update report state when state is unsupported" do @@ -352,6 +468,35 @@ defmodule Pleroma.Web.CommonAPITest do        assert CommonAPI.update_report_state(report_id, "test") == {:error, "Unsupported state"}      end + +    test "updates state of multiple reports" do +      [reporter, target_user] = insert_pair(:user) +      activity = insert(:note_activity, user: target_user) + +      {:ok, %Activity{id: first_report_id}} = +        CommonAPI.report(reporter, %{ +          "account_id" => target_user.id, +          "comment" => "I feel offended", +          "status_ids" => [activity.id] +        }) + +      {:ok, %Activity{id: second_report_id}} = +        CommonAPI.report(reporter, %{ +          "account_id" => target_user.id, +          "comment" => "I feel very offended!", +          "status_ids" => [activity.id] +        }) + +      {:ok, report_ids} = +        CommonAPI.update_report_state([first_report_id, second_report_id], "resolved") + +      first_report = Activity.get_by_id(first_report_id) +      second_report = Activity.get_by_id(second_report_id) + +      assert report_ids -- [first_report_id, second_report_id] == [] +      assert first_report.data["state"] == "resolved" +      assert second_report.data["state"] == "resolved" +    end    end    describe "reblog muting" do @@ -364,14 +509,14 @@ defmodule Pleroma.Web.CommonAPITest do      end      test "add a reblog mute", %{muter: muter, muted: muted} do -      {:ok, muter} = CommonAPI.hide_reblogs(muter, muted) +      {:ok, _reblog_mute} = CommonAPI.hide_reblogs(muter, muted)        assert User.showing_reblogs?(muter, muted) == false      end      test "remove a reblog mute", %{muter: muter, muted: muted} do -      {:ok, muter} = CommonAPI.hide_reblogs(muter, muted) -      {:ok, muter} = CommonAPI.show_reblogs(muter, muted) +      {:ok, _reblog_mute} = CommonAPI.hide_reblogs(muter, muted) +      {:ok, _reblog_mute} = CommonAPI.show_reblogs(muter, muted)        assert User.showing_reblogs?(muter, muted) == true      end @@ -381,7 +526,7 @@ defmodule Pleroma.Web.CommonAPITest do      test "also unsubscribes a user" do        [follower, followed] = insert_pair(:user)        {:ok, follower, followed, _} = CommonAPI.follow(follower, followed) -      {:ok, followed} = User.subscribe(follower, followed) +      {:ok, _subscription} = User.subscribe(follower, followed)        assert User.subscribed_to?(follower, followed) @@ -393,7 +538,7 @@ defmodule Pleroma.Web.CommonAPITest do    describe "accept_follow_request/2" do      test "after acceptance, it sets all existing pending follow request states to 'accept'" do -      user = insert(:user, info: %{locked: true}) +      user = insert(:user, locked: true)        follower = insert(:user)        follower_two = insert(:user) @@ -413,7 +558,7 @@ defmodule Pleroma.Web.CommonAPITest do      end      test "after rejection, it sets all existing pending follow request states to 'reject'" do -      user = insert(:user, info: %{locked: true}) +      user = insert(:user, locked: true)        follower = insert(:user)        follower_two = insert(:user) @@ -451,4 +596,43 @@ defmodule Pleroma.Web.CommonAPITest do        assert {:error, "Already voted"} == CommonAPI.vote(other_user, object, [1])      end    end + +  describe "listen/2" do +    test "returns a valid activity" do +      user = insert(:user) + +      {:ok, activity} = +        CommonAPI.listen(user, %{ +          "title" => "lain radio episode 1", +          "album" => "lain radio", +          "artist" => "lain", +          "length" => 180_000 +        }) + +      object = Object.normalize(activity) + +      assert object.data["title"] == "lain radio episode 1" + +      assert Visibility.get_visibility(activity) == "public" +    end + +    test "respects visibility=private" do +      user = insert(:user) + +      {:ok, activity} = +        CommonAPI.listen(user, %{ +          "title" => "lain radio episode 1", +          "album" => "lain radio", +          "artist" => "lain", +          "length" => 180_000, +          "visibility" => "private" +        }) + +      object = Object.normalize(activity) + +      assert object.data["title"] == "lain radio episode 1" + +      assert Visibility.get_visibility(activity) == "private" +    end +  end  end diff --git a/test/web/common_api/common_api_utils_test.exs b/test/web/common_api/common_api_utils_test.exs index af320f31f..4b761e039 100644 --- a/test/web/common_api/common_api_utils_test.exs +++ b/test/web/common_api/common_api_utils_test.exs @@ -1,5 +1,5 @@  # Pleroma: A lightweight social networking server -# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.Web.CommonAPI.UtilsTest do @@ -157,11 +157,11 @@ defmodule Pleroma.Web.CommonAPI.UtilsTest do        text = "**hello world**\n\n*another @user__test and @user__test google.com paragraph*"        expected = -        "<p><strong>hello world</strong></p>\n<p><em>another <span class=\"h-card\"><a data-user=\"#{ +        ~s(<p><strong>hello world</strong></p>\n<p><em>another <span class="h-card"><a data-user="#{            user.id -        }\" class=\"u-url mention\" href=\"http://foo.com/user__test\">@<span>user__test</span></a></span> and <span class=\"h-card\"><a data-user=\"#{ +        }" class="u-url mention" href="http://foo.com/user__test" rel="ugc">@<span>user__test</span></a></span> and <span class="h-card"><a data-user="#{            user.id -        }\" class=\"u-url mention\" href=\"http://foo.com/user__test\">@<span>user__test</span></a></span> <a href=\"http://google.com\">google.com</a> paragraph</em></p>\n" +        }" class="u-url mention" href="http://foo.com/user__test" rel="ugc">@<span>user__test</span></a></span> <a href="http://google.com" rel="ugc">google.com</a> paragraph</em></p>\n)        {output, _, _} = Utils.format_input(text, "text/markdown") @@ -239,7 +239,7 @@ defmodule Pleroma.Web.CommonAPI.UtilsTest do        mentioned_user = insert(:user)        mentions = [mentioned_user.ap_id] -      {to, cc} = Utils.get_to_and_cc(user, mentions, nil, "public") +      {to, cc} = Utils.get_to_and_cc(user, mentions, nil, "public", nil)        assert length(to) == 2        assert length(cc) == 1 @@ -256,7 +256,7 @@ defmodule Pleroma.Web.CommonAPI.UtilsTest do        {:ok, activity} = CommonAPI.post(third_user, %{"status" => "uguu"})        mentions = [mentioned_user.ap_id] -      {to, cc} = Utils.get_to_and_cc(user, mentions, activity, "public") +      {to, cc} = Utils.get_to_and_cc(user, mentions, activity, "public", nil)        assert length(to) == 3        assert length(cc) == 1 @@ -272,7 +272,7 @@ defmodule Pleroma.Web.CommonAPI.UtilsTest do        mentioned_user = insert(:user)        mentions = [mentioned_user.ap_id] -      {to, cc} = Utils.get_to_and_cc(user, mentions, nil, "unlisted") +      {to, cc} = Utils.get_to_and_cc(user, mentions, nil, "unlisted", nil)        assert length(to) == 2        assert length(cc) == 1 @@ -289,7 +289,7 @@ defmodule Pleroma.Web.CommonAPI.UtilsTest do        {:ok, activity} = CommonAPI.post(third_user, %{"status" => "uguu"})        mentions = [mentioned_user.ap_id] -      {to, cc} = Utils.get_to_and_cc(user, mentions, activity, "unlisted") +      {to, cc} = Utils.get_to_and_cc(user, mentions, activity, "unlisted", nil)        assert length(to) == 3        assert length(cc) == 1 @@ -305,10 +305,9 @@ defmodule Pleroma.Web.CommonAPI.UtilsTest do        mentioned_user = insert(:user)        mentions = [mentioned_user.ap_id] -      {to, cc} = Utils.get_to_and_cc(user, mentions, nil, "private") - +      {to, cc} = Utils.get_to_and_cc(user, mentions, nil, "private", nil)        assert length(to) == 2 -      assert length(cc) == 0 +      assert Enum.empty?(cc)        assert mentioned_user.ap_id in to        assert user.follower_address in to @@ -321,10 +320,10 @@ defmodule Pleroma.Web.CommonAPI.UtilsTest do        {:ok, activity} = CommonAPI.post(third_user, %{"status" => "uguu"})        mentions = [mentioned_user.ap_id] -      {to, cc} = Utils.get_to_and_cc(user, mentions, activity, "private") +      {to, cc} = Utils.get_to_and_cc(user, mentions, activity, "private", nil)        assert length(to) == 3 -      assert length(cc) == 0 +      assert Enum.empty?(cc)        assert mentioned_user.ap_id in to        assert third_user.ap_id in to @@ -336,10 +335,10 @@ defmodule Pleroma.Web.CommonAPI.UtilsTest do        mentioned_user = insert(:user)        mentions = [mentioned_user.ap_id] -      {to, cc} = Utils.get_to_and_cc(user, mentions, nil, "direct") +      {to, cc} = Utils.get_to_and_cc(user, mentions, nil, "direct", nil)        assert length(to) == 1 -      assert length(cc) == 0 +      assert Enum.empty?(cc)        assert mentioned_user.ap_id in to      end @@ -351,13 +350,251 @@ defmodule Pleroma.Web.CommonAPI.UtilsTest do        {:ok, activity} = CommonAPI.post(third_user, %{"status" => "uguu"})        mentions = [mentioned_user.ap_id] -      {to, cc} = Utils.get_to_and_cc(user, mentions, activity, "direct") +      {to, cc} = Utils.get_to_and_cc(user, mentions, activity, "direct", nil)        assert length(to) == 2 -      assert length(cc) == 0 +      assert Enum.empty?(cc)        assert mentioned_user.ap_id in to        assert third_user.ap_id in to      end    end + +  describe "get_by_id_or_ap_id/1" do +    test "get activity by id" do +      activity = insert(:note_activity) +      %Pleroma.Activity{} = note = Utils.get_by_id_or_ap_id(activity.id) +      assert note.id == activity.id +    end + +    test "get activity by ap_id" do +      activity = insert(:note_activity) +      %Pleroma.Activity{} = note = Utils.get_by_id_or_ap_id(activity.data["object"]) +      assert note.id == activity.id +    end + +    test "get activity by object when type isn't `Create` " do +      activity = insert(:like_activity) +      %Pleroma.Activity{} = like = Utils.get_by_id_or_ap_id(activity.id) +      assert like.data["object"] == activity.data["object"] +    end +  end + +  describe "to_master_date/1" do +    test "removes microseconds from date (NaiveDateTime)" do +      assert Utils.to_masto_date(~N[2015-01-23 23:50:07.123]) == "2015-01-23T23:50:07.000Z" +    end + +    test "removes microseconds from date (String)" do +      assert Utils.to_masto_date("2015-01-23T23:50:07.123Z") == "2015-01-23T23:50:07.000Z" +    end + +    test "returns empty string when date invalid" do +      assert Utils.to_masto_date("2015-01?23T23:50:07.123Z") == "" +    end +  end + +  describe "conversation_id_to_context/1" do +    test "returns id" do +      object = insert(:note) +      assert Utils.conversation_id_to_context(object.id) == object.data["id"] +    end + +    test "returns error if object not found" do +      assert Utils.conversation_id_to_context("123") == {:error, "No such conversation"} +    end +  end + +  describe "maybe_notify_mentioned_recipients/2" do +    test "returns recipients when activity is not `Create`" do +      activity = insert(:like_activity) +      assert Utils.maybe_notify_mentioned_recipients(["test"], activity) == ["test"] +    end + +    test "returns recipients from tag" do +      user = insert(:user) + +      object = +        insert(:note, +          user: user, +          data: %{ +            "tag" => [ +              %{"type" => "Hashtag"}, +              "", +              %{"type" => "Mention", "href" => "https://testing.pleroma.lol/users/lain"}, +              %{"type" => "Mention", "href" => "https://shitposter.club/user/5381"}, +              %{"type" => "Mention", "href" => "https://shitposter.club/user/5381"} +            ] +          } +        ) + +      activity = insert(:note_activity, user: user, note: object) + +      assert Utils.maybe_notify_mentioned_recipients(["test"], activity) == [ +               "test", +               "https://testing.pleroma.lol/users/lain", +               "https://shitposter.club/user/5381" +             ] +    end + +    test "returns recipients when object is map" do +      user = insert(:user) +      object = insert(:note, user: user) + +      activity = +        insert(:note_activity, +          user: user, +          note: object, +          data_attrs: %{ +            "object" => %{ +              "tag" => [ +                %{"type" => "Hashtag"}, +                "", +                %{"type" => "Mention", "href" => "https://testing.pleroma.lol/users/lain"}, +                %{"type" => "Mention", "href" => "https://shitposter.club/user/5381"}, +                %{"type" => "Mention", "href" => "https://shitposter.club/user/5381"} +              ] +            } +          } +        ) + +      Pleroma.Repo.delete(object) + +      assert Utils.maybe_notify_mentioned_recipients(["test"], activity) == [ +               "test", +               "https://testing.pleroma.lol/users/lain", +               "https://shitposter.club/user/5381" +             ] +    end + +    test "returns recipients when object not found" do +      user = insert(:user) +      object = insert(:note, user: user) + +      activity = insert(:note_activity, user: user, note: object) +      Pleroma.Repo.delete(object) + +      assert Utils.maybe_notify_mentioned_recipients(["test-test"], activity) == [ +               "test-test" +             ] +    end +  end + +  describe "attachments_from_ids_descs/2" do +    test "returns [] when attachment ids is empty" do +      assert Utils.attachments_from_ids_descs([], "{}") == [] +    end + +    test "returns list attachments with desc" do +      object = insert(:note) +      desc = Jason.encode!(%{object.id => "test-desc"}) + +      assert Utils.attachments_from_ids_descs(["#{object.id}", "34"], desc) == [ +               Map.merge(object.data, %{"name" => "test-desc"}) +             ] +    end +  end + +  describe "attachments_from_ids/1" do +    test "returns attachments with descs" do +      object = insert(:note) +      desc = Jason.encode!(%{object.id => "test-desc"}) + +      assert Utils.attachments_from_ids(%{ +               "media_ids" => ["#{object.id}"], +               "descriptions" => desc +             }) == [ +               Map.merge(object.data, %{"name" => "test-desc"}) +             ] +    end + +    test "returns attachments without descs" do +      object = insert(:note) +      assert Utils.attachments_from_ids(%{"media_ids" => ["#{object.id}"]}) == [object.data] +    end + +    test "returns [] when not pass media_ids" do +      assert Utils.attachments_from_ids(%{}) == [] +    end +  end + +  describe "maybe_add_list_data/3" do +    test "adds list params when found user list" do +      user = insert(:user) +      {:ok, %Pleroma.List{} = list} = Pleroma.List.create("title", user) + +      assert Utils.maybe_add_list_data(%{additional: %{}, object: %{}}, user, {:list, list.id}) == +               %{ +                 additional: %{"bcc" => [list.ap_id], "listMessage" => list.ap_id}, +                 object: %{"listMessage" => list.ap_id} +               } +    end + +    test "returns original params when list not found" do +      user = insert(:user) +      {:ok, %Pleroma.List{} = list} = Pleroma.List.create("title", insert(:user)) + +      assert Utils.maybe_add_list_data(%{additional: %{}, object: %{}}, user, {:list, list.id}) == +               %{additional: %{}, object: %{}} +    end +  end + +  describe "make_note_data/11" do +    test "returns note data" do +      user = insert(:user) +      note = insert(:note) +      user2 = insert(:user) +      user3 = insert(:user) + +      assert Utils.make_note_data( +               user.ap_id, +               [user2.ap_id], +               "2hu", +               "<h1>This is :moominmamma: note</h1>", +               [], +               note.id, +               [name: "jimm"], +               "test summary", +               [user3.ap_id], +               false, +               %{"custom_tag" => "test"} +             ) == %{ +               "actor" => user.ap_id, +               "attachment" => [], +               "cc" => [user3.ap_id], +               "content" => "<h1>This is :moominmamma: note</h1>", +               "context" => "2hu", +               "sensitive" => false, +               "summary" => "test summary", +               "tag" => ["jimm"], +               "to" => [user2.ap_id], +               "type" => "Note", +               "custom_tag" => "test" +             } +    end +  end + +  describe "maybe_add_attachments/3" do +    test "returns parsed results when no_links is true" do +      assert Utils.maybe_add_attachments( +               {"test", [], ["tags"]}, +               [], +               true +             ) == {"test", [], ["tags"]} +    end + +    test "adds attachments to parsed results" do +      attachment = %{"url" => [%{"href" => "SakuraPM.png"}]} + +      assert Utils.maybe_add_attachments( +               {"test", [], ["tags"]}, +               [attachment], +               false +             ) == { +               "test<br><a href=\"SakuraPM.png\" class='attachment'>SakuraPM.png</a>", +               [], +               ["tags"] +             } +    end +  end  end diff --git a/test/web/fallback_test.exs b/test/web/fallback_test.exs index cc78b3ae1..c13db9526 100644 --- a/test/web/fallback_test.exs +++ b/test/web/fallback_test.exs @@ -30,6 +30,10 @@ defmodule Pleroma.Web.FallbackTest do             |> json_response(404) == %{"error" => "Not implemented"}    end +  test "GET /pleroma/admin -> /pleroma/admin/", %{conn: conn} do +    assert redirected_to(get(conn, "/pleroma/admin")) =~ "/pleroma/admin/" +  end +    test "GET /*path", %{conn: conn} do      assert conn             |> get("/foo") diff --git a/test/web/federator_test.exs b/test/web/federator_test.exs index 69dd4d747..c224197c3 100644 --- a/test/web/federator_test.exs +++ b/test/web/federator_test.exs @@ -1,27 +1,34 @@  # Pleroma: A lightweight social networking server -# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.Web.FederatorTest do    alias Pleroma.Instances +  alias Pleroma.Tests.ObanHelpers    alias Pleroma.Web.CommonAPI    alias Pleroma.Web.Federator +  alias Pleroma.Workers.PublisherWorker +    use Pleroma.DataCase +  use Oban.Testing, repo: Pleroma.Repo +    import Pleroma.Factory    import Mock    setup_all do      Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end) -    config_path = [:instance, :federating] -    initial_setting = Pleroma.Config.get(config_path) - -    Pleroma.Config.put(config_path, true) -    on_exit(fn -> Pleroma.Config.put(config_path, initial_setting) end) -      :ok    end +  clear_config_all([:instance, :federating]) do +    Pleroma.Config.put([:instance, :federating], true) +  end + +  clear_config([:instance, :allow_relay]) +  clear_config([:instance, :rewrite_policy]) +  clear_config([:mrf_keyword]) +    describe "Publish an activity" do      setup do        user = insert(:user) @@ -42,6 +49,7 @@ defmodule Pleroma.Web.FederatorTest do      } do        with_mocks([relay_mock]) do          Federator.publish(activity) +        ObanHelpers.perform(all_enqueued(worker: PublisherWorker))        end        assert_received :relay_publish @@ -55,19 +63,15 @@ defmodule Pleroma.Web.FederatorTest do        with_mocks([relay_mock]) do          Federator.publish(activity) +        ObanHelpers.perform(all_enqueued(worker: PublisherWorker))        end        refute_received :relay_publish - -      Pleroma.Config.put([:instance, :allow_relay], true)      end    end    describe "Targets reachability filtering in `publish`" do -    test_with_mock "it federates only to reachable instances via AP", -                   Pleroma.Web.ActivityPub.Publisher, -                   [:passthrough], -                   [] do +    test "it federates only to reachable instances via AP" do        user = insert(:user)        {inbox1, inbox2} = @@ -77,14 +81,16 @@ defmodule Pleroma.Web.FederatorTest do          local: false,          nickname: "nick1@domain.com",          ap_id: "https://domain.com/users/nick1", -        info: %{ap_enabled: true, source_data: %{"inbox" => inbox1}} +        source_data: %{"inbox" => inbox1}, +        ap_enabled: true        })        insert(:user, %{          local: false,          nickname: "nick2@domain2.com",          ap_id: "https://domain2.com/users/nick2", -        info: %{ap_enabled: true, source_data: %{"inbox" => inbox2}} +        source_data: %{"inbox" => inbox2}, +        ap_enabled: true        })        dt = NaiveDateTime.utc_now() @@ -95,92 +101,17 @@ defmodule Pleroma.Web.FederatorTest do        {:ok, _activity} =          CommonAPI.post(user, %{"status" => "HI @nick1@domain.com, @nick2@domain2.com!"}) -      assert called( -               Pleroma.Web.ActivityPub.Publisher.publish_one(%{ -                 inbox: inbox1, -                 unreachable_since: dt -               }) -             ) - -      refute called(Pleroma.Web.ActivityPub.Publisher.publish_one(%{inbox: inbox2})) -    end +      expected_dt = NaiveDateTime.to_iso8601(dt) -    test_with_mock "it federates only to reachable instances via Websub", -                   Pleroma.Web.Websub, -                   [:passthrough], -                   [] do -      user = insert(:user) -      websub_topic = Pleroma.Web.OStatus.feed_path(user) - -      sub1 = -        insert(:websub_subscription, %{ -          topic: websub_topic, -          state: "active", -          callback: "http://pleroma.soykaf.com/cb" -        }) - -      sub2 = -        insert(:websub_subscription, %{ -          topic: websub_topic, -          state: "active", -          callback: "https://pleroma2.soykaf.com/cb" -        }) - -      dt = NaiveDateTime.utc_now() -      Instances.set_unreachable(sub2.callback, dt) +      ObanHelpers.perform(all_enqueued(worker: PublisherWorker)) -      Instances.set_consistently_unreachable(sub1.callback) - -      {:ok, _activity} = CommonAPI.post(user, %{"status" => "HI"}) - -      assert called( -               Pleroma.Web.Websub.publish_one(%{ -                 callback: sub2.callback, -                 unreachable_since: dt -               }) +      assert ObanHelpers.member?( +               %{ +                 "op" => "publish_one", +                 "params" => %{"inbox" => inbox1, "unreachable_since" => expected_dt} +               }, +               all_enqueued(worker: PublisherWorker)               ) - -      refute called(Pleroma.Web.Websub.publish_one(%{callback: sub1.callback})) -    end - -    test_with_mock "it federates only to reachable instances via Salmon", -                   Pleroma.Web.Salmon, -                   [:passthrough], -                   [] do -      user = insert(:user) - -      remote_user1 = -        insert(:user, %{ -          local: false, -          nickname: "nick1@domain.com", -          ap_id: "https://domain.com/users/nick1", -          info: %{salmon: "https://domain.com/salmon"} -        }) - -      remote_user2 = -        insert(:user, %{ -          local: false, -          nickname: "nick2@domain2.com", -          ap_id: "https://domain2.com/users/nick2", -          info: %{salmon: "https://domain2.com/salmon"} -        }) - -      dt = NaiveDateTime.utc_now() -      Instances.set_unreachable(remote_user2.ap_id, dt) - -      Instances.set_consistently_unreachable("domain.com") - -      {:ok, _activity} = -        CommonAPI.post(user, %{"status" => "HI @nick1@domain.com, @nick2@domain2.com!"}) - -      assert called( -               Pleroma.Web.Salmon.publish_one(%{ -                 recipient: remote_user2, -                 unreachable_since: dt -               }) -             ) - -      refute called(Pleroma.Web.Salmon.publish_one(%{recipient: remote_user1}))      end    end @@ -200,7 +131,8 @@ defmodule Pleroma.Web.FederatorTest do          "to" => ["https://www.w3.org/ns/activitystreams#Public"]        } -      {:ok, _activity} = Federator.incoming_ap_doc(params) +      assert {:ok, job} = Federator.incoming_ap_doc(params) +      assert {:ok, _activity} = ObanHelpers.perform(job)      end      test "rejects incoming AP docs with incorrect origin" do @@ -218,7 +150,24 @@ defmodule Pleroma.Web.FederatorTest do          "to" => ["https://www.w3.org/ns/activitystreams#Public"]        } -      :error = Federator.incoming_ap_doc(params) +      assert {:ok, job} = Federator.incoming_ap_doc(params) +      assert :error = ObanHelpers.perform(job) +    end + +    test "it does not crash if MRF rejects the post" do +      Pleroma.Config.put([:mrf_keyword, :reject], ["lain"]) + +      Pleroma.Config.put( +        [:instance, :rewrite_policy], +        Pleroma.Web.ActivityPub.MRF.KeywordPolicy +      ) + +      params = +        File.read!("test/fixtures/mastodon-post-activity.json") +        |> Poison.decode!() + +      assert {:ok, job} = Federator.incoming_ap_doc(params) +      assert :error = ObanHelpers.perform(job)      end    end  end diff --git a/test/web/feed/feed_controller_test.exs b/test/web/feed/feed_controller_test.exs new file mode 100644 index 000000000..6f61acf43 --- /dev/null +++ b/test/web/feed/feed_controller_test.exs @@ -0,0 +1,251 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.Feed.FeedControllerTest do +  use Pleroma.Web.ConnCase + +  import Pleroma.Factory +  import SweetXml + +  alias Pleroma.Object +  alias Pleroma.User + +  clear_config([:feed]) + +  test "gets a feed", %{conn: conn} do +    Pleroma.Config.put( +      [:feed, :post_title], +      %{max_length: 10, omission: "..."} +    ) + +    activity = insert(:note_activity) + +    note = +      insert(:note, +        data: %{ +          "content" => "This is :moominmamma: note ", +          "attachment" => [ +            %{ +              "url" => [%{"mediaType" => "image/png", "href" => "https://pleroma.gov/image.png"}] +            } +          ], +          "inReplyTo" => activity.data["id"] +        } +      ) + +    note_activity = insert(:note_activity, note: note) +    user = User.get_cached_by_ap_id(note_activity.data["actor"]) + +    note2 = +      insert(:note, +        user: user, +        data: %{"content" => "42 This is :moominmamma: note ", "inReplyTo" => activity.data["id"]} +      ) + +    _note_activity2 = insert(:note_activity, note: note2) +    object = Object.normalize(note_activity) + +    resp = +      conn +      |> put_req_header("content-type", "application/atom+xml") +      |> get("/users/#{user.nickname}/feed.atom") +      |> response(200) + +    activity_titles = +      resp +      |> SweetXml.parse() +      |> SweetXml.xpath(~x"//entry/title/text()"l) + +    assert activity_titles == ['42 This...', 'This is...'] +    assert resp =~ object.data["content"] +  end + +  test "returns 404 for a missing feed", %{conn: conn} do +    conn = +      conn +      |> put_req_header("content-type", "application/atom+xml") +      |> get("/users/nonexisting/feed.atom") + +    assert response(conn, 404) +  end + +  describe "feed_redirect" do +    test "undefined format. it redirects to feed", %{conn: conn} do +      note_activity = insert(:note_activity) +      user = User.get_cached_by_ap_id(note_activity.data["actor"]) + +      response = +        conn +        |> put_req_header("accept", "application/xml") +        |> get("/users/#{user.nickname}") +        |> response(302) + +      assert response == +               "<html><body>You are being <a href=\"#{Pleroma.Web.base_url()}/users/#{ +                 user.nickname +               }/feed.atom\">redirected</a>.</body></html>" +    end + +    test "undefined format. it returns error when user not found", %{conn: conn} do +      response = +        conn +        |> put_req_header("accept", "application/xml") +        |> get("/users/jimm") +        |> response(404) + +      assert response == ~S({"error":"Not found"}) +    end + +    test "activity+json format. it redirects on actual feed of user", %{conn: conn} do +      note_activity = insert(:note_activity) +      user = User.get_cached_by_ap_id(note_activity.data["actor"]) + +      response = +        conn +        |> put_req_header("accept", "application/activity+json") +        |> get("/users/#{user.nickname}") +        |> json_response(200) + +      assert response["endpoints"] == %{ +               "oauthAuthorizationEndpoint" => "#{Pleroma.Web.base_url()}/oauth/authorize", +               "oauthRegistrationEndpoint" => "#{Pleroma.Web.base_url()}/api/v1/apps", +               "oauthTokenEndpoint" => "#{Pleroma.Web.base_url()}/oauth/token", +               "sharedInbox" => "#{Pleroma.Web.base_url()}/inbox", +               "uploadMedia" => "#{Pleroma.Web.base_url()}/api/ap/upload_media" +             } + +      assert response["@context"] == [ +               "https://www.w3.org/ns/activitystreams", +               "http://localhost:4001/schemas/litepub-0.1.jsonld", +               %{"@language" => "und"} +             ] + +      assert Map.take(response, [ +               "followers", +               "following", +               "id", +               "inbox", +               "manuallyApprovesFollowers", +               "name", +               "outbox", +               "preferredUsername", +               "summary", +               "tag", +               "type", +               "url" +             ]) == %{ +               "followers" => "#{Pleroma.Web.base_url()}/users/#{user.nickname}/followers", +               "following" => "#{Pleroma.Web.base_url()}/users/#{user.nickname}/following", +               "id" => "#{Pleroma.Web.base_url()}/users/#{user.nickname}", +               "inbox" => "#{Pleroma.Web.base_url()}/users/#{user.nickname}/inbox", +               "manuallyApprovesFollowers" => false, +               "name" => user.name, +               "outbox" => "#{Pleroma.Web.base_url()}/users/#{user.nickname}/outbox", +               "preferredUsername" => user.nickname, +               "summary" => user.bio, +               "tag" => [], +               "type" => "Person", +               "url" => "#{Pleroma.Web.base_url()}/users/#{user.nickname}" +             } +    end + +    test "activity+json format. it returns error whe use not found", %{conn: conn} do +      response = +        conn +        |> put_req_header("accept", "application/activity+json") +        |> get("/users/jimm") +        |> json_response(404) + +      assert response == "Not found" +    end + +    test "json format. it redirects on actual feed of user", %{conn: conn} do +      note_activity = insert(:note_activity) +      user = User.get_cached_by_ap_id(note_activity.data["actor"]) + +      response = +        conn +        |> put_req_header("accept", "application/json") +        |> get("/users/#{user.nickname}") +        |> json_response(200) + +      assert response["endpoints"] == %{ +               "oauthAuthorizationEndpoint" => "#{Pleroma.Web.base_url()}/oauth/authorize", +               "oauthRegistrationEndpoint" => "#{Pleroma.Web.base_url()}/api/v1/apps", +               "oauthTokenEndpoint" => "#{Pleroma.Web.base_url()}/oauth/token", +               "sharedInbox" => "#{Pleroma.Web.base_url()}/inbox", +               "uploadMedia" => "#{Pleroma.Web.base_url()}/api/ap/upload_media" +             } + +      assert response["@context"] == [ +               "https://www.w3.org/ns/activitystreams", +               "http://localhost:4001/schemas/litepub-0.1.jsonld", +               %{"@language" => "und"} +             ] + +      assert Map.take(response, [ +               "followers", +               "following", +               "id", +               "inbox", +               "manuallyApprovesFollowers", +               "name", +               "outbox", +               "preferredUsername", +               "summary", +               "tag", +               "type", +               "url" +             ]) == %{ +               "followers" => "#{Pleroma.Web.base_url()}/users/#{user.nickname}/followers", +               "following" => "#{Pleroma.Web.base_url()}/users/#{user.nickname}/following", +               "id" => "#{Pleroma.Web.base_url()}/users/#{user.nickname}", +               "inbox" => "#{Pleroma.Web.base_url()}/users/#{user.nickname}/inbox", +               "manuallyApprovesFollowers" => false, +               "name" => user.name, +               "outbox" => "#{Pleroma.Web.base_url()}/users/#{user.nickname}/outbox", +               "preferredUsername" => user.nickname, +               "summary" => user.bio, +               "tag" => [], +               "type" => "Person", +               "url" => "#{Pleroma.Web.base_url()}/users/#{user.nickname}" +             } +    end + +    test "json format. it returns error whe use 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 "html format. it redirects on actual feed of user", %{conn: conn} do +      note_activity = insert(:note_activity) +      user = User.get_cached_by_ap_id(note_activity.data["actor"]) + +      response = +        conn +        |> get("/users/#{user.nickname}") +        |> response(200) + +      assert response == +               Fallback.RedirectController.redirector_with_meta( +                 conn, +                 %{user: user} +               ).resp_body +    end + +    test "html format. it returns error when user not found", %{conn: conn} do +      response = +        conn +        |> get("/users/jimm") +        |> json_response(404) + +      assert response == %{"error" => "Not found"} +    end +  end +end diff --git a/test/web/instances/instance_test.exs b/test/web/instances/instance_test.exs index d28730994..e54d708ad 100644 --- a/test/web/instances/instance_test.exs +++ b/test/web/instances/instance_test.exs @@ -1,5 +1,5 @@  # Pleroma: A lightweight social networking server -# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.Instances.InstanceTest do @@ -10,19 +10,14 @@ defmodule Pleroma.Instances.InstanceTest do    import Pleroma.Factory -  setup_all do -    config_path = [:instance, :federation_reachability_timeout_days] -    initial_setting = Pleroma.Config.get(config_path) - -    Pleroma.Config.put(config_path, 1) -    on_exit(fn -> Pleroma.Config.put(config_path, initial_setting) end) - -    :ok +  clear_config_all([:instance, :federation_reachability_timeout_days]) do +    Pleroma.Config.put([:instance, :federation_reachability_timeout_days], 1)    end    describe "set_reachable/1" do      test "clears `unreachable_since` of existing matching Instance record having non-nil `unreachable_since`" do -      instance = insert(:instance, unreachable_since: NaiveDateTime.utc_now()) +      unreachable_since = NaiveDateTime.to_iso8601(NaiveDateTime.utc_now()) +      instance = insert(:instance, unreachable_since: unreachable_since)        assert {:ok, instance} = Instance.set_reachable(instance.host)        refute instance.unreachable_since diff --git a/test/web/instances/instances_test.exs b/test/web/instances/instances_test.exs index f0d84edea..65b03b155 100644 --- a/test/web/instances/instances_test.exs +++ b/test/web/instances/instances_test.exs @@ -1,5 +1,5 @@  # Pleroma: A lightweight social networking server -# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.InstancesTest do @@ -7,14 +7,8 @@ defmodule Pleroma.InstancesTest do    use Pleroma.DataCase -  setup_all do -    config_path = [:instance, :federation_reachability_timeout_days] -    initial_setting = Pleroma.Config.get(config_path) - -    Pleroma.Config.put(config_path, 1) -    on_exit(fn -> Pleroma.Config.put(config_path, initial_setting) end) - -    :ok +  clear_config_all([:instance, :federation_reachability_timeout_days]) do +    Pleroma.Config.put([:instance, :federation_reachability_timeout_days], 1)    end    describe "reachable?/1" do diff --git a/test/web/masto_fe_controller_test.exs b/test/web/masto_fe_controller_test.exs new file mode 100644 index 000000000..f9870a852 --- /dev/null +++ b/test/web/masto_fe_controller_test.exs @@ -0,0 +1,85 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.MastodonAPI.MastoFEController do +  use Pleroma.Web.ConnCase + +  alias Pleroma.Config +  alias Pleroma.User + +  import Pleroma.Factory + +  clear_config([:instance, :public]) + +  test "put settings", %{conn: conn} do +    user = insert(:user) + +    conn = +      conn +      |> assign(:user, user) +      |> assign(:token, insert(:oauth_token, user: user, scopes: ["write:accounts"])) +      |> put("/api/web/settings", %{"data" => %{"programming" => "socks"}}) + +    assert _result = json_response(conn, 200) + +    user = User.get_cached_by_ap_id(user.ap_id) +    assert user.settings == %{"programming" => "socks"} +  end + +  describe "index/2 redirections" do +    setup %{conn: conn} do +      session_opts = [ +        store: :cookie, +        key: "_test", +        signing_salt: "cooldude" +      ] + +      conn = +        conn +        |> Plug.Session.call(Plug.Session.init(session_opts)) +        |> fetch_session() + +      test_path = "/web/statuses/test" +      %{conn: conn, path: test_path} +    end + +    test "redirects not logged-in users to the login page", %{conn: conn, path: path} do +      conn = get(conn, path) + +      assert conn.status == 302 +      assert redirected_to(conn) == "/web/login" +    end + +    test "redirects not logged-in users to the login page on private instances", %{ +      conn: conn, +      path: path +    } do +      Config.put([:instance, :public], false) + +      conn = get(conn, path) + +      assert conn.status == 302 +      assert redirected_to(conn) == "/web/login" +    end + +    test "does not redirect logged in users to the login page", %{conn: conn, path: path} do +      token = insert(:oauth_token, scopes: ["read"]) + +      conn = +        conn +        |> assign(:user, token.user) +        |> assign(:token, token) +        |> get(path) + +      assert conn.status == 200 +    end + +    test "saves referer path to session", %{conn: conn, path: path} do +      conn = get(conn, path) +      return_to = Plug.Conn.get_session(conn, :return_to) + +      assert return_to == path +    end +  end +end diff --git a/test/web/mastodon_api/controllers/account_controller/update_credentials_test.exs b/test/web/mastodon_api/controllers/account_controller/update_credentials_test.exs new file mode 100644 index 000000000..09bdc46e0 --- /dev/null +++ b/test/web/mastodon_api/controllers/account_controller/update_credentials_test.exs @@ -0,0 +1,360 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.MastodonAPI.MastodonAPIController.UpdateCredentialsTest do +  alias Pleroma.Repo +  alias Pleroma.User + +  use Pleroma.Web.ConnCase + +  import Pleroma.Factory +  clear_config([:instance, :max_account_fields]) + +  describe "updating credentials" do +    setup do: oauth_access(["write:accounts"]) + +    test "sets user settings in a generic way", %{conn: conn} do +      res_conn = +        patch(conn, "/api/v1/accounts/update_credentials", %{ +          "pleroma_settings_store" => %{ +            pleroma_fe: %{ +              theme: "bla" +            } +          } +        }) + +      assert user_data = json_response(res_conn, 200) +      assert user_data["pleroma"]["settings_store"] == %{"pleroma_fe" => %{"theme" => "bla"}} + +      user = Repo.get(User, user_data["id"]) + +      res_conn = +        conn +        |> assign(:user, user) +        |> patch("/api/v1/accounts/update_credentials", %{ +          "pleroma_settings_store" => %{ +            masto_fe: %{ +              theme: "bla" +            } +          } +        }) + +      assert user_data = json_response(res_conn, 200) + +      assert user_data["pleroma"]["settings_store"] == +               %{ +                 "pleroma_fe" => %{"theme" => "bla"}, +                 "masto_fe" => %{"theme" => "bla"} +               } + +      user = Repo.get(User, user_data["id"]) + +      res_conn = +        conn +        |> assign(:user, user) +        |> patch("/api/v1/accounts/update_credentials", %{ +          "pleroma_settings_store" => %{ +            masto_fe: %{ +              theme: "blub" +            } +          } +        }) + +      assert user_data = json_response(res_conn, 200) + +      assert user_data["pleroma"]["settings_store"] == +               %{ +                 "pleroma_fe" => %{"theme" => "bla"}, +                 "masto_fe" => %{"theme" => "blub"} +               } +    end + +    test "updates the user's bio", %{conn: conn} do +      user2 = insert(:user) + +      conn = +        patch(conn, "/api/v1/accounts/update_credentials", %{ +          "note" => "I drink #cofe with @#{user2.nickname}" +        }) + +      assert user_data = json_response(conn, 200) + +      assert user_data["note"] == +               ~s(I drink <a class="hashtag" data-tag="cofe" href="http://localhost:4001/tag/cofe">#cofe</a> with <span class="h-card"><a data-user="#{ +                 user2.id +               }" class="u-url mention" href="#{user2.ap_id}" rel="ugc">@<span>#{user2.nickname}</span></a></span>) +    end + +    test "updates the user's locking status", %{conn: conn} do +      conn = patch(conn, "/api/v1/accounts/update_credentials", %{locked: "true"}) + +      assert user_data = json_response(conn, 200) +      assert user_data["locked"] == true +    end + +    test "updates the user's allow_following_move", %{user: user, conn: conn} do +      assert user.allow_following_move == true + +      conn = patch(conn, "/api/v1/accounts/update_credentials", %{allow_following_move: "false"}) + +      assert refresh_record(user).allow_following_move == false +      assert user_data = json_response(conn, 200) +      assert user_data["pleroma"]["allow_following_move"] == false +    end + +    test "updates the user's default scope", %{conn: conn} do +      conn = patch(conn, "/api/v1/accounts/update_credentials", %{default_scope: "cofe"}) + +      assert user_data = json_response(conn, 200) +      assert user_data["source"]["privacy"] == "cofe" +    end + +    test "updates the user's hide_followers status", %{conn: conn} do +      conn = patch(conn, "/api/v1/accounts/update_credentials", %{hide_followers: "true"}) + +      assert user_data = json_response(conn, 200) +      assert user_data["pleroma"]["hide_followers"] == true +    end + +    test "updates the user's hide_followers_count and hide_follows_count", %{conn: conn} do +      conn = +        patch(conn, "/api/v1/accounts/update_credentials", %{ +          hide_followers_count: "true", +          hide_follows_count: "true" +        }) + +      assert user_data = json_response(conn, 200) +      assert user_data["pleroma"]["hide_followers_count"] == true +      assert user_data["pleroma"]["hide_follows_count"] == true +    end + +    test "updates the user's skip_thread_containment option", %{user: user, conn: conn} do +      response = +        conn +        |> patch("/api/v1/accounts/update_credentials", %{skip_thread_containment: "true"}) +        |> json_response(200) + +      assert response["pleroma"]["skip_thread_containment"] == true +      assert refresh_record(user).skip_thread_containment +    end + +    test "updates the user's hide_follows status", %{conn: conn} do +      conn = patch(conn, "/api/v1/accounts/update_credentials", %{hide_follows: "true"}) + +      assert user_data = json_response(conn, 200) +      assert user_data["pleroma"]["hide_follows"] == true +    end + +    test "updates the user's hide_favorites status", %{conn: conn} do +      conn = patch(conn, "/api/v1/accounts/update_credentials", %{hide_favorites: "true"}) + +      assert user_data = json_response(conn, 200) +      assert user_data["pleroma"]["hide_favorites"] == true +    end + +    test "updates the user's show_role status", %{conn: conn} do +      conn = patch(conn, "/api/v1/accounts/update_credentials", %{show_role: "false"}) + +      assert user_data = json_response(conn, 200) +      assert user_data["source"]["pleroma"]["show_role"] == false +    end + +    test "updates the user's no_rich_text status", %{conn: conn} do +      conn = patch(conn, "/api/v1/accounts/update_credentials", %{no_rich_text: "true"}) + +      assert user_data = json_response(conn, 200) +      assert user_data["source"]["pleroma"]["no_rich_text"] == true +    end + +    test "updates the user's name", %{conn: conn} do +      conn = +        patch(conn, "/api/v1/accounts/update_credentials", %{"display_name" => "markorepairs"}) + +      assert user_data = json_response(conn, 200) +      assert user_data["display_name"] == "markorepairs" +    end + +    test "updates the user's avatar", %{user: user, conn: conn} do +      new_avatar = %Plug.Upload{ +        content_type: "image/jpg", +        path: Path.absname("test/fixtures/image.jpg"), +        filename: "an_image.jpg" +      } + +      conn = patch(conn, "/api/v1/accounts/update_credentials", %{"avatar" => new_avatar}) + +      assert user_response = json_response(conn, 200) +      assert user_response["avatar"] != User.avatar_url(user) +    end + +    test "updates the user's banner", %{user: user, conn: conn} do +      new_header = %Plug.Upload{ +        content_type: "image/jpg", +        path: Path.absname("test/fixtures/image.jpg"), +        filename: "an_image.jpg" +      } + +      conn = patch(conn, "/api/v1/accounts/update_credentials", %{"header" => new_header}) + +      assert user_response = json_response(conn, 200) +      assert user_response["header"] != User.banner_url(user) +    end + +    test "updates the user's background", %{conn: conn} do +      new_header = %Plug.Upload{ +        content_type: "image/jpg", +        path: Path.absname("test/fixtures/image.jpg"), +        filename: "an_image.jpg" +      } + +      conn = +        patch(conn, "/api/v1/accounts/update_credentials", %{ +          "pleroma_background_image" => new_header +        }) + +      assert user_response = json_response(conn, 200) +      assert user_response["pleroma"]["background_image"] +    end + +    test "requires 'write:accounts' permission" do +      token1 = insert(:oauth_token, scopes: ["read"]) +      token2 = insert(:oauth_token, scopes: ["write", "follow"]) + +      for token <- [token1, token2] do +        conn = +          build_conn() +          |> put_req_header("authorization", "Bearer #{token.token}") +          |> patch("/api/v1/accounts/update_credentials", %{}) + +        if token == token1 do +          assert %{"error" => "Insufficient permissions: write:accounts."} == +                   json_response(conn, 403) +        else +          assert json_response(conn, 200) +        end +      end +    end + +    test "updates profile emojos", %{user: user, conn: conn} do +      note = "*sips :blank:*" +      name = "I am :firefox:" + +      ret_conn = +        patch(conn, "/api/v1/accounts/update_credentials", %{ +          "note" => note, +          "display_name" => name +        }) + +      assert json_response(ret_conn, 200) + +      conn = get(conn, "/api/v1/accounts/#{user.id}") + +      assert user_data = json_response(conn, 200) + +      assert user_data["note"] == note +      assert user_data["display_name"] == name +      assert [%{"shortcode" => "blank"}, %{"shortcode" => "firefox"}] = user_data["emojis"] +    end + +    test "update fields", %{conn: conn} do +      fields = [ +        %{"name" => "<a href=\"http://google.com\">foo</a>", "value" => "<script>bar</script>"}, +        %{"name" => "link", "value" => "cofe.io"} +      ] + +      account_data = +        conn +        |> patch("/api/v1/accounts/update_credentials", %{"fields_attributes" => fields}) +        |> json_response(200) + +      assert account_data["fields"] == [ +               %{"name" => "foo", "value" => "bar"}, +               %{"name" => "link", "value" => ~S(<a href="http://cofe.io" rel="ugc">cofe.io</a>)} +             ] + +      assert account_data["source"]["fields"] == [ +               %{ +                 "name" => "<a href=\"http://google.com\">foo</a>", +                 "value" => "<script>bar</script>" +               }, +               %{"name" => "link", "value" => "cofe.io"} +             ] + +      fields = +        [ +          "fields_attributes[1][name]=link", +          "fields_attributes[1][value]=cofe.io", +          "fields_attributes[0][name]=<a href=\"http://google.com\">foo</a>", +          "fields_attributes[0][value]=bar" +        ] +        |> Enum.join("&") + +      account = +        conn +        |> put_req_header("content-type", "application/x-www-form-urlencoded") +        |> patch("/api/v1/accounts/update_credentials", fields) +        |> json_response(200) + +      assert account["fields"] == [ +               %{"name" => "foo", "value" => "bar"}, +               %{"name" => "link", "value" => ~S(<a href="http://cofe.io" rel="ugc">cofe.io</a>)} +             ] + +      assert account["source"]["fields"] == [ +               %{ +                 "name" => "<a href=\"http://google.com\">foo</a>", +                 "value" => "bar" +               }, +               %{"name" => "link", "value" => "cofe.io"} +             ] + +      name_limit = Pleroma.Config.get([:instance, :account_field_name_length]) +      value_limit = Pleroma.Config.get([:instance, :account_field_value_length]) + +      long_value = Enum.map(0..value_limit, fn _ -> "x" end) |> Enum.join() + +      fields = [%{"name" => "<b>foo<b>", "value" => long_value}] + +      assert %{"error" => "Invalid request"} == +               conn +               |> patch("/api/v1/accounts/update_credentials", %{"fields_attributes" => fields}) +               |> json_response(403) + +      long_name = Enum.map(0..name_limit, fn _ -> "x" end) |> Enum.join() + +      fields = [%{"name" => long_name, "value" => "bar"}] + +      assert %{"error" => "Invalid request"} == +               conn +               |> patch("/api/v1/accounts/update_credentials", %{"fields_attributes" => fields}) +               |> json_response(403) + +      Pleroma.Config.put([:instance, :max_account_fields], 1) + +      fields = [ +        %{"name" => "<b>foo<b>", "value" => "<i>bar</i>"}, +        %{"name" => "link", "value" => "cofe.io"} +      ] + +      assert %{"error" => "Invalid request"} == +               conn +               |> patch("/api/v1/accounts/update_credentials", %{"fields_attributes" => fields}) +               |> json_response(403) + +      fields = [ +        %{"name" => "foo", "value" => ""}, +        %{"name" => "", "value" => "bar"} +      ] + +      account = +        conn +        |> patch("/api/v1/accounts/update_credentials", %{"fields_attributes" => fields}) +        |> json_response(200) + +      assert account["fields"] == [ +               %{"name" => "foo", "value" => ""} +             ] +    end +  end +end diff --git a/test/web/mastodon_api/controllers/account_controller_test.exs b/test/web/mastodon_api/controllers/account_controller_test.exs new file mode 100644 index 000000000..0d4860a42 --- /dev/null +++ b/test/web/mastodon_api/controllers/account_controller_test.exs @@ -0,0 +1,841 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do +  use Pleroma.Web.ConnCase + +  alias Pleroma.Repo +  alias Pleroma.User +  alias Pleroma.Web.ActivityPub.ActivityPub +  alias Pleroma.Web.ActivityPub.InternalFetchActor +  alias Pleroma.Web.CommonAPI +  alias Pleroma.Web.OAuth.Token + +  import Pleroma.Factory + +  describe "account fetching" do +    test "works by id" do +      user = insert(:user) + +      conn = +        build_conn() +        |> get("/api/v1/accounts/#{user.id}") + +      assert %{"id" => id} = json_response(conn, 200) +      assert id == to_string(user.id) + +      conn = +        build_conn() +        |> get("/api/v1/accounts/-1") + +      assert %{"error" => "Can't find user"} = json_response(conn, 404) +    end + +    test "works by nickname" do +      user = insert(:user) + +      conn = +        build_conn() +        |> get("/api/v1/accounts/#{user.nickname}") + +      assert %{"id" => id} = json_response(conn, 200) +      assert id == user.id +    end + +    test "works by nickname for remote users" do +      limit_to_local = Pleroma.Config.get([:instance, :limit_to_local_content]) +      Pleroma.Config.put([:instance, :limit_to_local_content], false) +      user = insert(:user, nickname: "user@example.com", local: false) + +      conn = +        build_conn() +        |> get("/api/v1/accounts/#{user.nickname}") + +      Pleroma.Config.put([:instance, :limit_to_local_content], limit_to_local) +      assert %{"id" => id} = json_response(conn, 200) +      assert id == user.id +    end + +    test "respects limit_to_local_content == :all for remote user nicknames" do +      limit_to_local = Pleroma.Config.get([:instance, :limit_to_local_content]) +      Pleroma.Config.put([:instance, :limit_to_local_content], :all) + +      user = insert(:user, nickname: "user@example.com", local: false) + +      conn = +        build_conn() +        |> get("/api/v1/accounts/#{user.nickname}") + +      Pleroma.Config.put([:instance, :limit_to_local_content], limit_to_local) +      assert json_response(conn, 404) +    end + +    test "respects limit_to_local_content == :unauthenticated for remote user nicknames" do +      limit_to_local = Pleroma.Config.get([:instance, :limit_to_local_content]) +      Pleroma.Config.put([:instance, :limit_to_local_content], :unauthenticated) + +      user = insert(:user, nickname: "user@example.com", local: false) +      reading_user = insert(:user) + +      conn = +        build_conn() +        |> get("/api/v1/accounts/#{user.nickname}") + +      assert json_response(conn, 404) + +      conn = +        build_conn() +        |> assign(:user, reading_user) +        |> assign(:token, insert(:oauth_token, user: reading_user, scopes: ["read:accounts"])) +        |> get("/api/v1/accounts/#{user.nickname}") + +      Pleroma.Config.put([:instance, :limit_to_local_content], limit_to_local) +      assert %{"id" => id} = json_response(conn, 200) +      assert id == user.id +    end + +    test "accounts fetches correct account for nicknames beginning with numbers", %{conn: conn} do +      # Need to set an old-style integer ID to reproduce the problem +      # (these are no longer assigned to new accounts but were preserved +      # for existing accounts during the migration to flakeIDs) +      user_one = insert(:user, %{id: 1212}) +      user_two = insert(:user, %{nickname: "#{user_one.id}garbage"}) + +      resp_one = +        conn +        |> get("/api/v1/accounts/#{user_one.id}") + +      resp_two = +        conn +        |> get("/api/v1/accounts/#{user_two.nickname}") + +      resp_three = +        conn +        |> get("/api/v1/accounts/#{user_two.id}") + +      acc_one = json_response(resp_one, 200) +      acc_two = json_response(resp_two, 200) +      acc_three = json_response(resp_three, 200) +      refute acc_one == acc_two +      assert acc_two == acc_three +    end + +    test "returns 404 when user is invisible", %{conn: conn} do +      user = insert(:user, %{invisible: true}) + +      resp = +        conn +        |> get("/api/v1/accounts/#{user.nickname}") +        |> json_response(404) + +      assert %{"error" => "Can't find user"} = resp +    end + +    test "returns 404 for internal.fetch actor", %{conn: conn} do +      %User{nickname: "internal.fetch"} = InternalFetchActor.get_actor() + +      resp = +        conn +        |> get("/api/v1/accounts/internal.fetch") +        |> json_response(404) + +      assert %{"error" => "Can't find user"} = resp +    end +  end + +  describe "user timelines" do +    setup do: oauth_access(["read:statuses"]) + +    test "respects blocks", %{user: user_one, conn: conn} do +      user_two = insert(:user) +      user_three = insert(:user) + +      User.block(user_one, user_two) + +      {:ok, activity} = CommonAPI.post(user_two, %{"status" => "User one sux0rz"}) +      {:ok, repeat, _} = CommonAPI.repeat(activity.id, user_three) + +      resp = get(conn, "/api/v1/accounts/#{user_two.id}/statuses") + +      assert [%{"id" => id}] = json_response(resp, 200) +      assert id == activity.id + +      # Even a blocked user will deliver the full user timeline, there would be +      #   no point in looking at a blocked users timeline otherwise +      resp = get(conn, "/api/v1/accounts/#{user_two.id}/statuses") + +      assert [%{"id" => id}] = json_response(resp, 200) +      assert id == activity.id + +      # Third user's timeline includes the repeat when viewed by unauthenticated user +      resp = get(build_conn(), "/api/v1/accounts/#{user_three.id}/statuses") +      assert [%{"id" => id}] = json_response(resp, 200) +      assert id == repeat.id + +      # When viewing a third user's timeline, the blocked users' statuses will NOT be shown +      resp = get(conn, "/api/v1/accounts/#{user_three.id}/statuses") + +      assert [] = json_response(resp, 200) +    end + +    test "gets users statuses", %{conn: conn} do +      user_one = insert(:user) +      user_two = insert(:user) +      user_three = insert(:user) + +      {:ok, _user_three} = User.follow(user_three, user_one) + +      {:ok, activity} = CommonAPI.post(user_one, %{"status" => "HI!!!"}) + +      {:ok, direct_activity} = +        CommonAPI.post(user_one, %{ +          "status" => "Hi, @#{user_two.nickname}.", +          "visibility" => "direct" +        }) + +      {:ok, private_activity} = +        CommonAPI.post(user_one, %{"status" => "private", "visibility" => "private"}) + +      resp = get(conn, "/api/v1/accounts/#{user_one.id}/statuses") + +      assert [%{"id" => id}] = json_response(resp, 200) +      assert id == to_string(activity.id) + +      resp = +        conn +        |> assign(:user, user_two) +        |> assign(:token, insert(:oauth_token, user: user_two, scopes: ["read:statuses"])) +        |> get("/api/v1/accounts/#{user_one.id}/statuses") + +      assert [%{"id" => id_one}, %{"id" => id_two}] = json_response(resp, 200) +      assert id_one == to_string(direct_activity.id) +      assert id_two == to_string(activity.id) + +      resp = +        conn +        |> assign(:user, user_three) +        |> assign(:token, insert(:oauth_token, user: user_three, scopes: ["read:statuses"])) +        |> get("/api/v1/accounts/#{user_one.id}/statuses") + +      assert [%{"id" => id_one}, %{"id" => id_two}] = json_response(resp, 200) +      assert id_one == to_string(private_activity.id) +      assert id_two == to_string(activity.id) +    end + +    test "unimplemented pinned statuses feature", %{conn: conn} do +      note = insert(:note_activity) +      user = User.get_cached_by_ap_id(note.data["actor"]) + +      conn = get(conn, "/api/v1/accounts/#{user.id}/statuses?pinned=true") + +      assert json_response(conn, 200) == [] +    end + +    test "gets an users media", %{conn: conn} do +      note = insert(:note_activity) +      user = User.get_cached_by_ap_id(note.data["actor"]) + +      file = %Plug.Upload{ +        content_type: "image/jpg", +        path: Path.absname("test/fixtures/image.jpg"), +        filename: "an_image.jpg" +      } + +      {:ok, %{id: media_id}} = ActivityPub.upload(file, actor: user.ap_id) + +      {:ok, image_post} = CommonAPI.post(user, %{"status" => "cofe", "media_ids" => [media_id]}) + +      conn = get(conn, "/api/v1/accounts/#{user.id}/statuses", %{"only_media" => "true"}) + +      assert [%{"id" => id}] = json_response(conn, 200) +      assert id == to_string(image_post.id) + +      conn = get(build_conn(), "/api/v1/accounts/#{user.id}/statuses", %{"only_media" => "1"}) + +      assert [%{"id" => id}] = json_response(conn, 200) +      assert id == to_string(image_post.id) +    end + +    test "gets a user's statuses without reblogs", %{user: user, conn: conn} do +      {:ok, post} = CommonAPI.post(user, %{"status" => "HI!!!"}) +      {:ok, _, _} = CommonAPI.repeat(post.id, user) + +      conn = get(conn, "/api/v1/accounts/#{user.id}/statuses", %{"exclude_reblogs" => "true"}) + +      assert [%{"id" => id}] = json_response(conn, 200) +      assert id == to_string(post.id) + +      conn = get(conn, "/api/v1/accounts/#{user.id}/statuses", %{"exclude_reblogs" => "1"}) + +      assert [%{"id" => id}] = json_response(conn, 200) +      assert id == to_string(post.id) +    end + +    test "filters user's statuses by a hashtag", %{user: user, conn: conn} do +      {:ok, post} = CommonAPI.post(user, %{"status" => "#hashtag"}) +      {:ok, _post} = CommonAPI.post(user, %{"status" => "hashtag"}) + +      conn = get(conn, "/api/v1/accounts/#{user.id}/statuses", %{"tagged" => "hashtag"}) + +      assert [%{"id" => id}] = json_response(conn, 200) +      assert id == to_string(post.id) +    end + +    test "the user views their own timelines and excludes direct messages", %{ +      user: user, +      conn: conn +    } do +      {:ok, public_activity} = CommonAPI.post(user, %{"status" => ".", "visibility" => "public"}) +      {:ok, _direct_activity} = CommonAPI.post(user, %{"status" => ".", "visibility" => "direct"}) + +      conn = +        get(conn, "/api/v1/accounts/#{user.id}/statuses", %{"exclude_visibilities" => ["direct"]}) + +      assert [%{"id" => id}] = json_response(conn, 200) +      assert id == to_string(public_activity.id) +    end +  end + +  describe "followers" do +    setup do: oauth_access(["read:accounts"]) + +    test "getting followers", %{user: user, conn: conn} do +      other_user = insert(:user) +      {:ok, user} = User.follow(user, other_user) + +      conn = get(conn, "/api/v1/accounts/#{other_user.id}/followers") + +      assert [%{"id" => id}] = json_response(conn, 200) +      assert id == to_string(user.id) +    end + +    test "getting followers, hide_followers", %{user: user, conn: conn} do +      other_user = insert(:user, hide_followers: true) +      {:ok, _user} = User.follow(user, other_user) + +      conn = get(conn, "/api/v1/accounts/#{other_user.id}/followers") + +      assert [] == json_response(conn, 200) +    end + +    test "getting followers, hide_followers, same user requesting" do +      user = insert(:user) +      other_user = insert(:user, hide_followers: true) +      {:ok, _user} = User.follow(user, other_user) + +      conn = +        build_conn() +        |> assign(:user, other_user) +        |> assign(:token, insert(:oauth_token, user: other_user, scopes: ["read:accounts"])) +        |> get("/api/v1/accounts/#{other_user.id}/followers") + +      refute [] == json_response(conn, 200) +    end + +    test "getting followers, pagination", %{user: user, conn: conn} do +      follower1 = insert(:user) +      follower2 = insert(:user) +      follower3 = insert(:user) +      {:ok, _} = User.follow(follower1, user) +      {:ok, _} = User.follow(follower2, user) +      {:ok, _} = User.follow(follower3, user) + +      res_conn = get(conn, "/api/v1/accounts/#{user.id}/followers?since_id=#{follower1.id}") + +      assert [%{"id" => id3}, %{"id" => id2}] = json_response(res_conn, 200) +      assert id3 == follower3.id +      assert id2 == follower2.id + +      res_conn = get(conn, "/api/v1/accounts/#{user.id}/followers?max_id=#{follower3.id}") + +      assert [%{"id" => id2}, %{"id" => id1}] = json_response(res_conn, 200) +      assert id2 == follower2.id +      assert id1 == follower1.id + +      res_conn = get(conn, "/api/v1/accounts/#{user.id}/followers?limit=1&max_id=#{follower3.id}") + +      assert [%{"id" => id2}] = json_response(res_conn, 200) +      assert id2 == follower2.id + +      assert [link_header] = get_resp_header(res_conn, "link") +      assert link_header =~ ~r/min_id=#{follower2.id}/ +      assert link_header =~ ~r/max_id=#{follower2.id}/ +    end +  end + +  describe "following" do +    setup do: oauth_access(["read:accounts"]) + +    test "getting following", %{user: user, conn: conn} do +      other_user = insert(:user) +      {:ok, user} = User.follow(user, other_user) + +      conn = get(conn, "/api/v1/accounts/#{user.id}/following") + +      assert [%{"id" => id}] = json_response(conn, 200) +      assert id == to_string(other_user.id) +    end + +    test "getting following, hide_follows, other user requesting" do +      user = insert(:user, hide_follows: true) +      other_user = insert(:user) +      {:ok, user} = User.follow(user, other_user) + +      conn = +        build_conn() +        |> assign(:user, other_user) +        |> assign(:token, insert(:oauth_token, user: other_user, scopes: ["read:accounts"])) +        |> get("/api/v1/accounts/#{user.id}/following") + +      assert [] == json_response(conn, 200) +    end + +    test "getting following, hide_follows, same user requesting" do +      user = insert(:user, hide_follows: true) +      other_user = insert(:user) +      {:ok, user} = User.follow(user, other_user) + +      conn = +        build_conn() +        |> assign(:user, user) +        |> assign(:token, insert(:oauth_token, user: user, scopes: ["read:accounts"])) +        |> get("/api/v1/accounts/#{user.id}/following") + +      refute [] == json_response(conn, 200) +    end + +    test "getting following, pagination", %{user: user, conn: conn} do +      following1 = insert(:user) +      following2 = insert(:user) +      following3 = insert(:user) +      {:ok, _} = User.follow(user, following1) +      {:ok, _} = User.follow(user, following2) +      {:ok, _} = User.follow(user, following3) + +      res_conn = get(conn, "/api/v1/accounts/#{user.id}/following?since_id=#{following1.id}") + +      assert [%{"id" => id3}, %{"id" => id2}] = json_response(res_conn, 200) +      assert id3 == following3.id +      assert id2 == following2.id + +      res_conn = get(conn, "/api/v1/accounts/#{user.id}/following?max_id=#{following3.id}") + +      assert [%{"id" => id2}, %{"id" => id1}] = json_response(res_conn, 200) +      assert id2 == following2.id +      assert id1 == following1.id + +      res_conn = +        get(conn, "/api/v1/accounts/#{user.id}/following?limit=1&max_id=#{following3.id}") + +      assert [%{"id" => id2}] = json_response(res_conn, 200) +      assert id2 == following2.id + +      assert [link_header] = get_resp_header(res_conn, "link") +      assert link_header =~ ~r/min_id=#{following2.id}/ +      assert link_header =~ ~r/max_id=#{following2.id}/ +    end +  end + +  describe "follow/unfollow" do +    setup do: oauth_access(["follow"]) + +    test "following / unfollowing a user", %{conn: conn} do +      other_user = insert(:user) + +      ret_conn = post(conn, "/api/v1/accounts/#{other_user.id}/follow") + +      assert %{"id" => _id, "following" => true} = json_response(ret_conn, 200) + +      ret_conn = post(conn, "/api/v1/accounts/#{other_user.id}/unfollow") + +      assert %{"id" => _id, "following" => false} = json_response(ret_conn, 200) + +      conn = post(conn, "/api/v1/follows", %{"uri" => other_user.nickname}) + +      assert %{"id" => id} = json_response(conn, 200) +      assert id == to_string(other_user.id) +    end + +    test "following without reblogs" do +      %{conn: conn} = oauth_access(["follow", "read:statuses"]) +      followed = insert(:user) +      other_user = insert(:user) + +      ret_conn = post(conn, "/api/v1/accounts/#{followed.id}/follow?reblogs=false") + +      assert %{"showing_reblogs" => false} = json_response(ret_conn, 200) + +      {:ok, activity} = CommonAPI.post(other_user, %{"status" => "hey"}) +      {:ok, reblog, _} = CommonAPI.repeat(activity.id, followed) + +      ret_conn = get(conn, "/api/v1/timelines/home") + +      assert [] == json_response(ret_conn, 200) + +      ret_conn = post(conn, "/api/v1/accounts/#{followed.id}/follow?reblogs=true") + +      assert %{"showing_reblogs" => true} = json_response(ret_conn, 200) + +      conn = get(conn, "/api/v1/timelines/home") + +      expected_activity_id = reblog.id +      assert [%{"id" => ^expected_activity_id}] = json_response(conn, 200) +    end + +    test "following / unfollowing errors", %{user: user, conn: conn} do +      # self follow +      conn_res = post(conn, "/api/v1/accounts/#{user.id}/follow") +      assert %{"error" => "Record not found"} = json_response(conn_res, 404) + +      # self unfollow +      user = User.get_cached_by_id(user.id) +      conn_res = post(conn, "/api/v1/accounts/#{user.id}/unfollow") +      assert %{"error" => "Record not found"} = json_response(conn_res, 404) + +      # self follow via uri +      user = User.get_cached_by_id(user.id) +      conn_res = post(conn, "/api/v1/follows", %{"uri" => user.nickname}) +      assert %{"error" => "Record not found"} = json_response(conn_res, 404) + +      # follow non existing user +      conn_res = post(conn, "/api/v1/accounts/doesntexist/follow") +      assert %{"error" => "Record not found"} = json_response(conn_res, 404) + +      # follow non existing user via uri +      conn_res = post(conn, "/api/v1/follows", %{"uri" => "doesntexist"}) +      assert %{"error" => "Record not found"} = json_response(conn_res, 404) + +      # unfollow non existing user +      conn_res = post(conn, "/api/v1/accounts/doesntexist/unfollow") +      assert %{"error" => "Record not found"} = json_response(conn_res, 404) +    end +  end + +  describe "mute/unmute" do +    setup do: oauth_access(["write:mutes"]) + +    test "with notifications", %{conn: conn} do +      other_user = insert(:user) + +      ret_conn = post(conn, "/api/v1/accounts/#{other_user.id}/mute") + +      response = json_response(ret_conn, 200) + +      assert %{"id" => _id, "muting" => true, "muting_notifications" => true} = response + +      conn = post(conn, "/api/v1/accounts/#{other_user.id}/unmute") + +      response = json_response(conn, 200) +      assert %{"id" => _id, "muting" => false, "muting_notifications" => false} = response +    end + +    test "without notifications", %{conn: conn} do +      other_user = insert(:user) + +      ret_conn = +        post(conn, "/api/v1/accounts/#{other_user.id}/mute", %{"notifications" => "false"}) + +      response = json_response(ret_conn, 200) + +      assert %{"id" => _id, "muting" => true, "muting_notifications" => false} = response + +      conn = post(conn, "/api/v1/accounts/#{other_user.id}/unmute") + +      response = json_response(conn, 200) +      assert %{"id" => _id, "muting" => false, "muting_notifications" => false} = response +    end +  end + +  describe "pinned statuses" do +    setup do +      user = insert(:user) +      {:ok, activity} = CommonAPI.post(user, %{"status" => "HI!!!"}) +      %{conn: conn} = oauth_access(["read:statuses"], user: user) + +      [conn: conn, user: user, activity: activity] +    end + +    test "returns pinned statuses", %{conn: conn, user: user, activity: activity} do +      {:ok, _} = CommonAPI.pin(activity.id, user) + +      result = +        conn +        |> get("/api/v1/accounts/#{user.id}/statuses?pinned=true") +        |> json_response(200) + +      id_str = to_string(activity.id) + +      assert [%{"id" => ^id_str, "pinned" => true}] = result +    end +  end + +  test "blocking / unblocking a user" do +    %{conn: conn} = oauth_access(["follow"]) +    other_user = insert(:user) + +    ret_conn = post(conn, "/api/v1/accounts/#{other_user.id}/block") + +    assert %{"id" => _id, "blocking" => true} = json_response(ret_conn, 200) + +    conn = post(conn, "/api/v1/accounts/#{other_user.id}/unblock") + +    assert %{"id" => _id, "blocking" => false} = json_response(conn, 200) +  end + +  describe "create account by app" do +    setup do +      valid_params = %{ +        username: "lain", +        email: "lain@example.org", +        password: "PlzDontHackLain", +        agreement: true +      } + +      [valid_params: valid_params] +    end + +    test "Account registration via Application", %{conn: conn} do +      conn = +        post(conn, "/api/v1/apps", %{ +          client_name: "client_name", +          redirect_uris: "urn:ietf:wg:oauth:2.0:oob", +          scopes: "read, write, follow" +        }) + +      %{ +        "client_id" => client_id, +        "client_secret" => client_secret, +        "id" => _, +        "name" => "client_name", +        "redirect_uri" => "urn:ietf:wg:oauth:2.0:oob", +        "vapid_key" => _, +        "website" => nil +      } = json_response(conn, 200) + +      conn = +        post(conn, "/oauth/token", %{ +          grant_type: "client_credentials", +          client_id: client_id, +          client_secret: client_secret +        }) + +      assert %{"access_token" => token, "refresh_token" => refresh, "scope" => scope} = +               json_response(conn, 200) + +      assert token +      token_from_db = Repo.get_by(Token, token: token) +      assert token_from_db +      assert refresh +      assert scope == "read write follow" + +      conn = +        build_conn() +        |> put_req_header("authorization", "Bearer " <> token) +        |> post("/api/v1/accounts", %{ +          username: "lain", +          email: "lain@example.org", +          password: "PlzDontHackLain", +          bio: "Test Bio", +          agreement: true +        }) + +      %{ +        "access_token" => token, +        "created_at" => _created_at, +        "scope" => _scope, +        "token_type" => "Bearer" +      } = json_response(conn, 200) + +      token_from_db = Repo.get_by(Token, token: token) +      assert token_from_db +      token_from_db = Repo.preload(token_from_db, :user) +      assert token_from_db.user + +      assert token_from_db.user.confirmation_pending +    end + +    test "returns error when user already registred", %{conn: conn, valid_params: valid_params} do +      _user = insert(:user, email: "lain@example.org") +      app_token = insert(:oauth_token, user: nil) + +      conn = +        conn +        |> put_req_header("authorization", "Bearer " <> app_token.token) + +      res = post(conn, "/api/v1/accounts", valid_params) +      assert json_response(res, 400) == %{"error" => "{\"email\":[\"has already been taken\"]}"} +    end + +    test "rate limit", %{conn: conn} do +      app_token = insert(:oauth_token, user: nil) + +      conn = +        conn +        |> put_req_header("authorization", "Bearer " <> app_token.token) +        |> Map.put(:remote_ip, {15, 15, 15, 15}) + +      for i <- 1..5 do +        conn = +          post(conn, "/api/v1/accounts", %{ +            username: "#{i}lain", +            email: "#{i}lain@example.org", +            password: "PlzDontHackLain", +            agreement: true +          }) + +        %{ +          "access_token" => token, +          "created_at" => _created_at, +          "scope" => _scope, +          "token_type" => "Bearer" +        } = json_response(conn, 200) + +        token_from_db = Repo.get_by(Token, token: token) +        assert token_from_db +        token_from_db = Repo.preload(token_from_db, :user) +        assert token_from_db.user + +        assert token_from_db.user.confirmation_pending +      end + +      conn = +        post(conn, "/api/v1/accounts", %{ +          username: "6lain", +          email: "6lain@example.org", +          password: "PlzDontHackLain", +          agreement: true +        }) + +      assert json_response(conn, :too_many_requests) == %{"error" => "Throttled"} +    end + +    test "returns bad_request if missing required params", %{ +      conn: conn, +      valid_params: valid_params +    } do +      app_token = insert(:oauth_token, user: nil) + +      conn = put_req_header(conn, "authorization", "Bearer " <> app_token.token) + +      res = post(conn, "/api/v1/accounts", valid_params) +      assert json_response(res, 200) + +      [{127, 0, 0, 1}, {127, 0, 0, 2}, {127, 0, 0, 3}, {127, 0, 0, 4}] +      |> Stream.zip(valid_params) +      |> Enum.each(fn {ip, {attr, _}} -> +        res = +          conn +          |> Map.put(:remote_ip, ip) +          |> post("/api/v1/accounts", Map.delete(valid_params, attr)) +          |> json_response(400) + +        assert res == %{"error" => "Missing parameters"} +      end) +    end + +    test "returns forbidden if token is invalid", %{conn: conn, valid_params: valid_params} do +      conn = put_req_header(conn, "authorization", "Bearer " <> "invalid-token") + +      res = post(conn, "/api/v1/accounts", valid_params) +      assert json_response(res, 403) == %{"error" => "Invalid credentials"} +    end +  end + +  describe "GET /api/v1/accounts/:id/lists - account_lists" do +    test "returns lists to which the account belongs" do +      %{user: user, conn: conn} = oauth_access(["read:lists"]) +      other_user = insert(:user) +      assert {:ok, %Pleroma.List{} = list} = Pleroma.List.create("Test List", user) +      {:ok, %{following: _following}} = Pleroma.List.follow(list, other_user) + +      res = +        conn +        |> get("/api/v1/accounts/#{other_user.id}/lists") +        |> json_response(200) + +      assert res == [%{"id" => to_string(list.id), "title" => "Test List"}] +    end +  end + +  describe "verify_credentials" do +    test "verify_credentials" do +      %{user: user, conn: conn} = oauth_access(["read:accounts"]) +      conn = get(conn, "/api/v1/accounts/verify_credentials") + +      response = json_response(conn, 200) + +      assert %{"id" => id, "source" => %{"privacy" => "public"}} = response +      assert response["pleroma"]["chat_token"] +      assert id == to_string(user.id) +    end + +    test "verify_credentials default scope unlisted" do +      user = insert(:user, default_scope: "unlisted") +      %{conn: conn} = oauth_access(["read:accounts"], user: user) + +      conn = get(conn, "/api/v1/accounts/verify_credentials") + +      assert %{"id" => id, "source" => %{"privacy" => "unlisted"}} = json_response(conn, 200) +      assert id == to_string(user.id) +    end + +    test "locked accounts" do +      user = insert(:user, default_scope: "private") +      %{conn: conn} = oauth_access(["read:accounts"], user: user) + +      conn = get(conn, "/api/v1/accounts/verify_credentials") + +      assert %{"id" => id, "source" => %{"privacy" => "private"}} = json_response(conn, 200) +      assert id == to_string(user.id) +    end +  end + +  describe "user relationships" do +    setup do: oauth_access(["read:follows"]) + +    test "returns the relationships for the current user", %{user: user, conn: conn} do +      other_user = insert(:user) +      {:ok, _user} = User.follow(user, other_user) + +      conn = get(conn, "/api/v1/accounts/relationships", %{"id" => [other_user.id]}) + +      assert [relationship] = json_response(conn, 200) + +      assert to_string(other_user.id) == relationship["id"] +    end + +    test "returns an empty list on a bad request", %{conn: conn} do +      conn = get(conn, "/api/v1/accounts/relationships", %{}) + +      assert [] = json_response(conn, 200) +    end +  end + +  test "getting a list of mutes" do +    %{user: user, conn: conn} = oauth_access(["read:mutes"]) +    other_user = insert(:user) + +    {:ok, _user_relationships} = User.mute(user, other_user) + +    conn = get(conn, "/api/v1/mutes") + +    other_user_id = to_string(other_user.id) +    assert [%{"id" => ^other_user_id}] = json_response(conn, 200) +  end + +  test "getting a list of blocks" do +    %{user: user, conn: conn} = oauth_access(["read:blocks"]) +    other_user = insert(:user) + +    {:ok, _user_relationship} = User.block(user, other_user) + +    conn = +      conn +      |> assign(:user, user) +      |> get("/api/v1/blocks") + +    other_user_id = to_string(other_user.id) +    assert [%{"id" => ^other_user_id}] = json_response(conn, 200) +  end +end diff --git a/test/web/mastodon_api/controllers/app_controller_test.exs b/test/web/mastodon_api/controllers/app_controller_test.exs new file mode 100644 index 000000000..51788155b --- /dev/null +++ b/test/web/mastodon_api/controllers/app_controller_test.exs @@ -0,0 +1,60 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.MastodonAPI.AppControllerTest do +  use Pleroma.Web.ConnCase, async: true + +  alias Pleroma.Repo +  alias Pleroma.Web.OAuth.App +  alias Pleroma.Web.Push + +  import Pleroma.Factory + +  test "apps/verify_credentials", %{conn: conn} do +    token = insert(:oauth_token) + +    conn = +      conn +      |> assign(:user, token.user) +      |> assign(:token, token) +      |> get("/api/v1/apps/verify_credentials") + +    app = Repo.preload(token, :app).app + +    expected = %{ +      "name" => app.client_name, +      "website" => app.website, +      "vapid_key" => Push.vapid_config() |> Keyword.get(:public_key) +    } + +    assert expected == json_response(conn, 200) +  end + +  test "creates an oauth app", %{conn: conn} do +    user = insert(:user) +    app_attrs = build(:oauth_app) + +    conn = +      conn +      |> assign(:user, user) +      |> post("/api/v1/apps", %{ +        client_name: app_attrs.client_name, +        redirect_uris: app_attrs.redirect_uris +      }) + +    [app] = Repo.all(App) + +    expected = %{ +      "name" => app.client_name, +      "website" => app.website, +      "client_id" => app.client_id, +      "client_secret" => app.client_secret, +      "id" => app.id |> to_string(), +      "redirect_uri" => app.redirect_uris, +      "vapid_key" => Push.vapid_config() |> Keyword.get(:public_key) +    } + +    assert expected == json_response(conn, 200) +  end +end diff --git a/test/web/mastodon_api/controllers/auth_controller_test.exs b/test/web/mastodon_api/controllers/auth_controller_test.exs new file mode 100644 index 000000000..98b2a82e7 --- /dev/null +++ b/test/web/mastodon_api/controllers/auth_controller_test.exs @@ -0,0 +1,121 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.MastodonAPI.AuthControllerTest do +  use Pleroma.Web.ConnCase + +  alias Pleroma.Config +  alias Pleroma.Repo +  alias Pleroma.Tests.ObanHelpers + +  import Pleroma.Factory +  import Swoosh.TestAssertions + +  describe "GET /web/login" do +    setup %{conn: conn} do +      session_opts = [ +        store: :cookie, +        key: "_test", +        signing_salt: "cooldude" +      ] + +      conn = +        conn +        |> Plug.Session.call(Plug.Session.init(session_opts)) +        |> fetch_session() + +      test_path = "/web/statuses/test" +      %{conn: conn, path: test_path} +    end + +    test "redirects to the saved path after log in", %{conn: conn, path: path} do +      app = insert(:oauth_app, client_name: "Mastodon-Local", redirect_uris: ".") +      auth = insert(:oauth_authorization, app: app) + +      conn = +        conn +        |> put_session(:return_to, path) +        |> get("/web/login", %{code: auth.token}) + +      assert conn.status == 302 +      assert redirected_to(conn) == path +    end + +    test "redirects to the getting-started page when referer is not present", %{conn: conn} do +      app = insert(:oauth_app, client_name: "Mastodon-Local", redirect_uris: ".") +      auth = insert(:oauth_authorization, app: app) + +      conn = get(conn, "/web/login", %{code: auth.token}) + +      assert conn.status == 302 +      assert redirected_to(conn) == "/web/getting-started" +    end +  end + +  describe "POST /auth/password, with valid parameters" do +    setup %{conn: conn} do +      user = insert(:user) +      conn = post(conn, "/auth/password?email=#{user.email}") +      %{conn: conn, user: user} +    end + +    test "it returns 204", %{conn: conn} do +      assert json_response(conn, :no_content) +    end + +    test "it creates a PasswordResetToken record for user", %{user: user} do +      token_record = Repo.get_by(Pleroma.PasswordResetToken, user_id: user.id) +      assert token_record +    end + +    test "it sends an email to user", %{user: user} do +      ObanHelpers.perform_all() +      token_record = Repo.get_by(Pleroma.PasswordResetToken, user_id: user.id) + +      email = Pleroma.Emails.UserEmail.password_reset_email(user, token_record.token) +      notify_email = Config.get([:instance, :notify_email]) +      instance_name = Config.get([:instance, :name]) + +      assert_email_sent( +        from: {instance_name, notify_email}, +        to: {user.name, user.email}, +        html_body: email.html_body +      ) +    end +  end + +  describe "POST /auth/password, with invalid parameters" do +    setup do +      user = insert(:user) +      {:ok, user: user} +    end + +    test "it returns 404 when user is not found", %{conn: conn, user: user} do +      conn = post(conn, "/auth/password?email=nonexisting_#{user.email}") +      assert conn.status == 404 +      assert conn.resp_body == "" +    end + +    test "it returns 400 when user is not local", %{conn: conn, user: user} do +      {:ok, user} = Repo.update(Ecto.Changeset.change(user, local: false)) +      conn = post(conn, "/auth/password?email=#{user.email}") +      assert conn.status == 400 +      assert conn.resp_body == "" +    end +  end + +  describe "DELETE /auth/sign_out" do +    test "redirect to root page", %{conn: conn} do +      user = insert(:user) + +      conn = +        conn +        |> assign(:user, user) +        |> delete("/auth/sign_out") + +      assert conn.status == 302 +      assert redirected_to(conn) == "/" +    end +  end +end diff --git a/test/web/mastodon_api/controllers/conversation_controller_test.exs b/test/web/mastodon_api/controllers/conversation_controller_test.exs new file mode 100644 index 000000000..4bb9781a6 --- /dev/null +++ b/test/web/mastodon_api/controllers/conversation_controller_test.exs @@ -0,0 +1,208 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.MastodonAPI.ConversationControllerTest do +  use Pleroma.Web.ConnCase + +  alias Pleroma.User +  alias Pleroma.Web.CommonAPI + +  import Pleroma.Factory + +  setup do: oauth_access(["read:statuses"]) + +  test "returns a list of conversations", %{user: user_one, conn: conn} do +    user_two = insert(:user) +    user_three = insert(:user) + +    {:ok, user_two} = User.follow(user_two, user_one) + +    assert User.get_cached_by_id(user_two.id).unread_conversation_count == 0 + +    {:ok, direct} = +      CommonAPI.post(user_one, %{ +        "status" => "Hi @#{user_two.nickname}, @#{user_three.nickname}!", +        "visibility" => "direct" +      }) + +    assert User.get_cached_by_id(user_two.id).unread_conversation_count == 1 + +    {:ok, _follower_only} = +      CommonAPI.post(user_one, %{ +        "status" => "Hi @#{user_two.nickname}!", +        "visibility" => "private" +      }) + +    res_conn = get(conn, "/api/v1/conversations") + +    assert response = json_response(res_conn, 200) + +    assert [ +             %{ +               "id" => res_id, +               "accounts" => res_accounts, +               "last_status" => res_last_status, +               "unread" => unread +             } +           ] = response + +    account_ids = Enum.map(res_accounts, & &1["id"]) +    assert length(res_accounts) == 2 +    assert user_two.id in account_ids +    assert user_three.id in account_ids +    assert is_binary(res_id) +    assert unread == false +    assert res_last_status["id"] == direct.id +    assert User.get_cached_by_id(user_one.id).unread_conversation_count == 0 +  end + +  test "filters conversations by recipients", %{user: user_one, conn: conn} do +    user_two = insert(:user) +    user_three = insert(:user) + +    {:ok, direct1} = +      CommonAPI.post(user_one, %{ +        "status" => "Hi @#{user_two.nickname}!", +        "visibility" => "direct" +      }) + +    {:ok, _direct2} = +      CommonAPI.post(user_one, %{ +        "status" => "Hi @#{user_three.nickname}!", +        "visibility" => "direct" +      }) + +    {:ok, direct3} = +      CommonAPI.post(user_one, %{ +        "status" => "Hi @#{user_two.nickname}, @#{user_three.nickname}!", +        "visibility" => "direct" +      }) + +    {:ok, _direct4} = +      CommonAPI.post(user_two, %{ +        "status" => "Hi @#{user_three.nickname}!", +        "visibility" => "direct" +      }) + +    {:ok, direct5} = +      CommonAPI.post(user_two, %{ +        "status" => "Hi @#{user_one.nickname}!", +        "visibility" => "direct" +      }) + +    [conversation1, conversation2] = +      conn +      |> get("/api/v1/conversations", %{"recipients" => [user_two.id]}) +      |> json_response(200) + +    assert conversation1["last_status"]["id"] == direct5.id +    assert conversation2["last_status"]["id"] == direct1.id + +    [conversation1] = +      conn +      |> get("/api/v1/conversations", %{"recipients" => [user_two.id, user_three.id]}) +      |> json_response(200) + +    assert conversation1["last_status"]["id"] == direct3.id +  end + +  test "updates the last_status on reply", %{user: user_one, conn: conn} do +    user_two = insert(:user) + +    {:ok, direct} = +      CommonAPI.post(user_one, %{ +        "status" => "Hi @#{user_two.nickname}", +        "visibility" => "direct" +      }) + +    {:ok, direct_reply} = +      CommonAPI.post(user_two, %{ +        "status" => "reply", +        "visibility" => "direct", +        "in_reply_to_status_id" => direct.id +      }) + +    [%{"last_status" => res_last_status}] = +      conn +      |> get("/api/v1/conversations") +      |> json_response(200) + +    assert res_last_status["id"] == direct_reply.id +  end + +  test "the user marks a conversation as read", %{user: user_one, conn: conn} do +    user_two = insert(:user) + +    {:ok, direct} = +      CommonAPI.post(user_one, %{ +        "status" => "Hi @#{user_two.nickname}", +        "visibility" => "direct" +      }) + +    assert User.get_cached_by_id(user_one.id).unread_conversation_count == 0 +    assert User.get_cached_by_id(user_two.id).unread_conversation_count == 1 + +    user_two_conn = +      build_conn() +      |> assign(:user, user_two) +      |> assign( +        :token, +        insert(:oauth_token, user: user_two, scopes: ["read:statuses", "write:conversations"]) +      ) + +    [%{"id" => direct_conversation_id, "unread" => true}] = +      user_two_conn +      |> get("/api/v1/conversations") +      |> json_response(200) + +    %{"unread" => false} = +      user_two_conn +      |> post("/api/v1/conversations/#{direct_conversation_id}/read") +      |> json_response(200) + +    assert User.get_cached_by_id(user_one.id).unread_conversation_count == 0 +    assert User.get_cached_by_id(user_two.id).unread_conversation_count == 0 + +    # The conversation is marked as unread on reply +    {:ok, _} = +      CommonAPI.post(user_two, %{ +        "status" => "reply", +        "visibility" => "direct", +        "in_reply_to_status_id" => direct.id +      }) + +    [%{"unread" => true}] = +      conn +      |> get("/api/v1/conversations") +      |> json_response(200) + +    assert User.get_cached_by_id(user_one.id).unread_conversation_count == 1 +    assert User.get_cached_by_id(user_two.id).unread_conversation_count == 0 + +    # A reply doesn't increment the user's unread_conversation_count if the conversation is unread +    {:ok, _} = +      CommonAPI.post(user_two, %{ +        "status" => "reply", +        "visibility" => "direct", +        "in_reply_to_status_id" => direct.id +      }) + +    assert User.get_cached_by_id(user_one.id).unread_conversation_count == 1 +    assert User.get_cached_by_id(user_two.id).unread_conversation_count == 0 +  end + +  test "(vanilla) Mastodon frontend behaviour", %{user: user_one, conn: conn} do +    user_two = insert(:user) + +    {:ok, direct} = +      CommonAPI.post(user_one, %{ +        "status" => "Hi @#{user_two.nickname}!", +        "visibility" => "direct" +      }) + +    res_conn = get(conn, "/api/v1/statuses/#{direct.id}/context") + +    assert %{"ancestors" => [], "descendants" => []} == json_response(res_conn, 200) +  end +end diff --git a/test/web/mastodon_api/controllers/custom_emoji_controller_test.exs b/test/web/mastodon_api/controllers/custom_emoji_controller_test.exs new file mode 100644 index 000000000..2d988b0b8 --- /dev/null +++ b/test/web/mastodon_api/controllers/custom_emoji_controller_test.exs @@ -0,0 +1,22 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.MastodonAPI.CustomEmojiControllerTest do +  use Pleroma.Web.ConnCase, async: true + +  test "with tags", %{conn: conn} do +    [emoji | _body] = +      conn +      |> get("/api/v1/custom_emojis") +      |> json_response(200) + +    assert Map.has_key?(emoji, "shortcode") +    assert Map.has_key?(emoji, "static_url") +    assert Map.has_key?(emoji, "tags") +    assert is_list(emoji["tags"]) +    assert Map.has_key?(emoji, "category") +    assert Map.has_key?(emoji, "url") +    assert Map.has_key?(emoji, "visible_in_picker") +  end +end diff --git a/test/web/mastodon_api/controllers/domain_block_controller_test.exs b/test/web/mastodon_api/controllers/domain_block_controller_test.exs new file mode 100644 index 000000000..55de625ba --- /dev/null +++ b/test/web/mastodon_api/controllers/domain_block_controller_test.exs @@ -0,0 +1,45 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.MastodonAPI.DomainBlockControllerTest do +  use Pleroma.Web.ConnCase + +  alias Pleroma.User + +  import Pleroma.Factory + +  test "blocking / unblocking a domain" do +    %{user: user, conn: conn} = oauth_access(["write:blocks"]) +    other_user = insert(:user, %{ap_id: "https://dogwhistle.zone/@pundit"}) + +    ret_conn = post(conn, "/api/v1/domain_blocks", %{"domain" => "dogwhistle.zone"}) + +    assert %{} = json_response(ret_conn, 200) +    user = User.get_cached_by_ap_id(user.ap_id) +    assert User.blocks?(user, other_user) + +    ret_conn = delete(conn, "/api/v1/domain_blocks", %{"domain" => "dogwhistle.zone"}) + +    assert %{} = json_response(ret_conn, 200) +    user = User.get_cached_by_ap_id(user.ap_id) +    refute User.blocks?(user, other_user) +  end + +  test "getting a list of domain blocks" do +    %{user: user, conn: conn} = oauth_access(["read:blocks"]) + +    {:ok, user} = User.block_domain(user, "bad.site") +    {:ok, user} = User.block_domain(user, "even.worse.site") + +    conn = +      conn +      |> assign(:user, user) +      |> get("/api/v1/domain_blocks") + +    domain_blocks = json_response(conn, 200) + +    assert "bad.site" in domain_blocks +    assert "even.worse.site" in domain_blocks +  end +end diff --git a/test/web/mastodon_api/controllers/filter_controller_test.exs b/test/web/mastodon_api/controllers/filter_controller_test.exs new file mode 100644 index 000000000..3aea17ec7 --- /dev/null +++ b/test/web/mastodon_api/controllers/filter_controller_test.exs @@ -0,0 +1,123 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.MastodonAPI.FilterControllerTest do +  use Pleroma.Web.ConnCase + +  alias Pleroma.Web.MastodonAPI.FilterView + +  test "creating a filter" do +    %{conn: conn} = oauth_access(["write:filters"]) + +    filter = %Pleroma.Filter{ +      phrase: "knights", +      context: ["home"] +    } + +    conn = post(conn, "/api/v1/filters", %{"phrase" => filter.phrase, context: filter.context}) + +    assert response = json_response(conn, 200) +    assert response["phrase"] == filter.phrase +    assert response["context"] == filter.context +    assert response["irreversible"] == false +    assert response["id"] != nil +    assert response["id"] != "" +  end + +  test "fetching a list of filters" do +    %{user: user, conn: conn} = oauth_access(["read:filters"]) + +    query_one = %Pleroma.Filter{ +      user_id: user.id, +      filter_id: 1, +      phrase: "knights", +      context: ["home"] +    } + +    query_two = %Pleroma.Filter{ +      user_id: user.id, +      filter_id: 2, +      phrase: "who", +      context: ["home"] +    } + +    {:ok, filter_one} = Pleroma.Filter.create(query_one) +    {:ok, filter_two} = Pleroma.Filter.create(query_two) + +    response = +      conn +      |> get("/api/v1/filters") +      |> json_response(200) + +    assert response == +             render_json( +               FilterView, +               "filters.json", +               filters: [filter_two, filter_one] +             ) +  end + +  test "get a filter" do +    %{user: user, conn: conn} = oauth_access(["read:filters"]) + +    query = %Pleroma.Filter{ +      user_id: user.id, +      filter_id: 2, +      phrase: "knight", +      context: ["home"] +    } + +    {:ok, filter} = Pleroma.Filter.create(query) + +    conn = get(conn, "/api/v1/filters/#{filter.filter_id}") + +    assert _response = json_response(conn, 200) +  end + +  test "update a filter" do +    %{user: user, conn: conn} = oauth_access(["write:filters"]) + +    query = %Pleroma.Filter{ +      user_id: user.id, +      filter_id: 2, +      phrase: "knight", +      context: ["home"] +    } + +    {:ok, _filter} = Pleroma.Filter.create(query) + +    new = %Pleroma.Filter{ +      phrase: "nii", +      context: ["home"] +    } + +    conn = +      put(conn, "/api/v1/filters/#{query.filter_id}", %{ +        phrase: new.phrase, +        context: new.context +      }) + +    assert response = json_response(conn, 200) +    assert response["phrase"] == new.phrase +    assert response["context"] == new.context +  end + +  test "delete a filter" do +    %{user: user, conn: conn} = oauth_access(["write:filters"]) + +    query = %Pleroma.Filter{ +      user_id: user.id, +      filter_id: 2, +      phrase: "knight", +      context: ["home"] +    } + +    {:ok, filter} = Pleroma.Filter.create(query) + +    conn = delete(conn, "/api/v1/filters/#{filter.filter_id}") + +    assert response = json_response(conn, 200) +    assert response == %{} +  end +end diff --git a/test/web/mastodon_api/controllers/follow_request_controller_test.exs b/test/web/mastodon_api/controllers/follow_request_controller_test.exs new file mode 100644 index 000000000..6e4a76501 --- /dev/null +++ b/test/web/mastodon_api/controllers/follow_request_controller_test.exs @@ -0,0 +1,74 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.MastodonAPI.FollowRequestControllerTest do +  use Pleroma.Web.ConnCase + +  alias Pleroma.User +  alias Pleroma.Web.ActivityPub.ActivityPub + +  import Pleroma.Factory + +  describe "locked accounts" do +    setup do +      user = insert(:user, locked: true) +      %{conn: conn} = oauth_access(["follow"], user: user) +      %{user: user, conn: conn} +    end + +    test "/api/v1/follow_requests works", %{user: user, conn: conn} do +      other_user = insert(:user) + +      {:ok, _activity} = ActivityPub.follow(other_user, user) +      {:ok, other_user} = User.follow(other_user, user, "pending") + +      assert User.following?(other_user, user) == false + +      conn = get(conn, "/api/v1/follow_requests") + +      assert [relationship] = json_response(conn, 200) +      assert to_string(other_user.id) == relationship["id"] +    end + +    test "/api/v1/follow_requests/:id/authorize works", %{user: user, conn: conn} do +      other_user = insert(:user) + +      {:ok, _activity} = ActivityPub.follow(other_user, user) +      {:ok, other_user} = User.follow(other_user, user, "pending") + +      user = User.get_cached_by_id(user.id) +      other_user = User.get_cached_by_id(other_user.id) + +      assert User.following?(other_user, user) == false + +      conn = post(conn, "/api/v1/follow_requests/#{other_user.id}/authorize") + +      assert relationship = json_response(conn, 200) +      assert to_string(other_user.id) == relationship["id"] + +      user = User.get_cached_by_id(user.id) +      other_user = User.get_cached_by_id(other_user.id) + +      assert User.following?(other_user, user) == true +    end + +    test "/api/v1/follow_requests/:id/reject works", %{user: user, conn: conn} do +      other_user = insert(:user) + +      {:ok, _activity} = ActivityPub.follow(other_user, user) + +      user = User.get_cached_by_id(user.id) + +      conn = post(conn, "/api/v1/follow_requests/#{other_user.id}/reject") + +      assert relationship = json_response(conn, 200) +      assert to_string(other_user.id) == relationship["id"] + +      user = User.get_cached_by_id(user.id) +      other_user = User.get_cached_by_id(other_user.id) + +      assert User.following?(other_user, user) == false +    end +  end +end diff --git a/test/web/mastodon_api/controllers/instance_controller_test.exs b/test/web/mastodon_api/controllers/instance_controller_test.exs new file mode 100644 index 000000000..e00de6b18 --- /dev/null +++ b/test/web/mastodon_api/controllers/instance_controller_test.exs @@ -0,0 +1,77 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.MastodonAPI.InstanceControllerTest do +  use Pleroma.Web.ConnCase + +  alias Pleroma.User +  import Pleroma.Factory + +  test "get instance information", %{conn: conn} do +    conn = get(conn, "/api/v1/instance") +    assert result = json_response(conn, 200) + +    email = Pleroma.Config.get([:instance, :email]) +    # Note: not checking for "max_toot_chars" since it's optional +    assert %{ +             "uri" => _, +             "title" => _, +             "description" => _, +             "version" => _, +             "email" => from_config_email, +             "urls" => %{ +               "streaming_api" => _ +             }, +             "stats" => _, +             "thumbnail" => _, +             "languages" => _, +             "registrations" => _, +             "poll_limits" => _, +             "upload_limit" => _, +             "avatar_upload_limit" => _, +             "background_upload_limit" => _, +             "banner_upload_limit" => _ +           } = result + +    assert email == from_config_email +  end + +  test "get instance stats", %{conn: conn} do +    user = insert(:user, %{local: true}) + +    user2 = insert(:user, %{local: true}) +    {:ok, _user2} = User.deactivate(user2, !user2.deactivated) + +    insert(:user, %{local: false, nickname: "u@peer1.com"}) +    insert(:user, %{local: false, nickname: "u@peer2.com"}) + +    {:ok, _} = Pleroma.Web.CommonAPI.post(user, %{"status" => "cofe"}) + +    Pleroma.Stats.force_update() + +    conn = get(conn, "/api/v1/instance") + +    assert result = json_response(conn, 200) + +    stats = result["stats"] + +    assert stats +    assert stats["user_count"] == 1 +    assert stats["status_count"] == 1 +    assert stats["domain_count"] == 2 +  end + +  test "get peers", %{conn: conn} do +    insert(:user, %{local: false, nickname: "u@peer1.com"}) +    insert(:user, %{local: false, nickname: "u@peer2.com"}) + +    Pleroma.Stats.force_update() + +    conn = get(conn, "/api/v1/instance/peers") + +    assert result = json_response(conn, 200) + +    assert ["peer1.com", "peer2.com"] == Enum.sort(result) +  end +end diff --git a/test/web/mastodon_api/controllers/list_controller_test.exs b/test/web/mastodon_api/controllers/list_controller_test.exs new file mode 100644 index 000000000..a6effbb69 --- /dev/null +++ b/test/web/mastodon_api/controllers/list_controller_test.exs @@ -0,0 +1,142 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.MastodonAPI.ListControllerTest do +  use Pleroma.Web.ConnCase + +  alias Pleroma.Repo + +  import Pleroma.Factory + +  test "creating a list" do +    %{conn: conn} = oauth_access(["write:lists"]) + +    conn = post(conn, "/api/v1/lists", %{"title" => "cuties"}) + +    assert %{"title" => title} = json_response(conn, 200) +    assert title == "cuties" +  end + +  test "renders error for invalid params" do +    %{conn: conn} = oauth_access(["write:lists"]) + +    conn = post(conn, "/api/v1/lists", %{"title" => nil}) + +    assert %{"error" => "can't be blank"} == json_response(conn, :unprocessable_entity) +  end + +  test "listing a user's lists" do +    %{conn: conn} = oauth_access(["read:lists", "write:lists"]) + +    conn +    |> post("/api/v1/lists", %{"title" => "cuties"}) +    |> json_response(:ok) + +    conn +    |> post("/api/v1/lists", %{"title" => "cofe"}) +    |> json_response(:ok) + +    conn = get(conn, "/api/v1/lists") + +    assert [ +             %{"id" => _, "title" => "cofe"}, +             %{"id" => _, "title" => "cuties"} +           ] = json_response(conn, :ok) +  end + +  test "adding users to a list" do +    %{user: user, conn: conn} = oauth_access(["write:lists"]) +    other_user = insert(:user) +    {:ok, list} = Pleroma.List.create("name", user) + +    conn = post(conn, "/api/v1/lists/#{list.id}/accounts", %{"account_ids" => [other_user.id]}) + +    assert %{} == json_response(conn, 200) +    %Pleroma.List{following: following} = Pleroma.List.get(list.id, user) +    assert following == [other_user.follower_address] +  end + +  test "removing users from a list" do +    %{user: user, conn: conn} = oauth_access(["write:lists"]) +    other_user = insert(:user) +    third_user = insert(:user) +    {:ok, list} = Pleroma.List.create("name", user) +    {:ok, list} = Pleroma.List.follow(list, other_user) +    {:ok, list} = Pleroma.List.follow(list, third_user) + +    conn = delete(conn, "/api/v1/lists/#{list.id}/accounts", %{"account_ids" => [other_user.id]}) + +    assert %{} == json_response(conn, 200) +    %Pleroma.List{following: following} = Pleroma.List.get(list.id, user) +    assert following == [third_user.follower_address] +  end + +  test "listing users in a list" do +    %{user: user, conn: conn} = oauth_access(["read:lists"]) +    other_user = insert(:user) +    {:ok, list} = Pleroma.List.create("name", user) +    {:ok, list} = Pleroma.List.follow(list, other_user) + +    conn = +      conn +      |> assign(:user, user) +      |> get("/api/v1/lists/#{list.id}/accounts", %{"account_ids" => [other_user.id]}) + +    assert [%{"id" => id}] = json_response(conn, 200) +    assert id == to_string(other_user.id) +  end + +  test "retrieving a list" do +    %{user: user, conn: conn} = oauth_access(["read:lists"]) +    {:ok, list} = Pleroma.List.create("name", user) + +    conn = +      conn +      |> assign(:user, user) +      |> get("/api/v1/lists/#{list.id}") + +    assert %{"id" => id} = json_response(conn, 200) +    assert id == to_string(list.id) +  end + +  test "renders 404 if list is not found" do +    %{conn: conn} = oauth_access(["read:lists"]) + +    conn = get(conn, "/api/v1/lists/666") + +    assert %{"error" => "List not found"} = json_response(conn, :not_found) +  end + +  test "renaming a list" do +    %{user: user, conn: conn} = oauth_access(["write:lists"]) +    {:ok, list} = Pleroma.List.create("name", user) + +    conn = put(conn, "/api/v1/lists/#{list.id}", %{"title" => "newname"}) + +    assert %{"title" => name} = json_response(conn, 200) +    assert name == "newname" +  end + +  test "validates title when renaming a list" do +    %{user: user, conn: conn} = oauth_access(["write:lists"]) +    {:ok, list} = Pleroma.List.create("name", user) + +    conn = +      conn +      |> assign(:user, user) +      |> put("/api/v1/lists/#{list.id}", %{"title" => "  "}) + +    assert %{"error" => "can't be blank"} == json_response(conn, :unprocessable_entity) +  end + +  test "deleting a list" do +    %{user: user, conn: conn} = oauth_access(["write:lists"]) +    {:ok, list} = Pleroma.List.create("name", user) + +    conn = delete(conn, "/api/v1/lists/#{list.id}") + +    assert %{} = json_response(conn, 200) +    assert is_nil(Repo.get(Pleroma.List, list.id)) +  end +end diff --git a/test/web/mastodon_api/controllers/marker_controller_test.exs b/test/web/mastodon_api/controllers/marker_controller_test.exs new file mode 100644 index 000000000..1fcad873d --- /dev/null +++ b/test/web/mastodon_api/controllers/marker_controller_test.exs @@ -0,0 +1,124 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.MastodonAPI.MarkerControllerTest do +  use Pleroma.Web.ConnCase + +  import Pleroma.Factory + +  describe "GET /api/v1/markers" do +    test "gets markers with correct scopes", %{conn: conn} do +      user = insert(:user) +      token = insert(:oauth_token, user: user, scopes: ["read:statuses"]) + +      {:ok, %{"notifications" => marker}} = +        Pleroma.Marker.upsert( +          user, +          %{"notifications" => %{"last_read_id" => "69420"}} +        ) + +      response = +        conn +        |> assign(:user, user) +        |> assign(:token, token) +        |> get("/api/v1/markers", %{timeline: ["notifications"]}) +        |> json_response(200) + +      assert response == %{ +               "notifications" => %{ +                 "last_read_id" => "69420", +                 "updated_at" => NaiveDateTime.to_iso8601(marker.updated_at), +                 "version" => 0 +               } +             } +    end + +    test "gets markers with missed scopes", %{conn: conn} do +      user = insert(:user) +      token = insert(:oauth_token, user: user, scopes: []) + +      Pleroma.Marker.upsert(user, %{"notifications" => %{"last_read_id" => "69420"}}) + +      response = +        conn +        |> assign(:user, user) +        |> assign(:token, token) +        |> get("/api/v1/markers", %{timeline: ["notifications"]}) +        |> json_response(403) + +      assert response == %{"error" => "Insufficient permissions: read:statuses."} +    end +  end + +  describe "POST /api/v1/markers" do +    test "creates a marker with correct scopes", %{conn: conn} do +      user = insert(:user) +      token = insert(:oauth_token, user: user, scopes: ["write:statuses"]) + +      response = +        conn +        |> assign(:user, user) +        |> assign(:token, token) +        |> post("/api/v1/markers", %{ +          home: %{last_read_id: "777"}, +          notifications: %{"last_read_id" => "69420"} +        }) +        |> json_response(200) + +      assert %{ +               "notifications" => %{ +                 "last_read_id" => "69420", +                 "updated_at" => _, +                 "version" => 0 +               } +             } = response +    end + +    test "updates exist marker", %{conn: conn} do +      user = insert(:user) +      token = insert(:oauth_token, user: user, scopes: ["write:statuses"]) + +      {:ok, %{"notifications" => marker}} = +        Pleroma.Marker.upsert( +          user, +          %{"notifications" => %{"last_read_id" => "69477"}} +        ) + +      response = +        conn +        |> assign(:user, user) +        |> assign(:token, token) +        |> post("/api/v1/markers", %{ +          home: %{last_read_id: "777"}, +          notifications: %{"last_read_id" => "69888"} +        }) +        |> json_response(200) + +      assert response == %{ +               "notifications" => %{ +                 "last_read_id" => "69888", +                 "updated_at" => NaiveDateTime.to_iso8601(marker.updated_at), +                 "version" => 0 +               } +             } +    end + +    test "creates a marker with missed scopes", %{conn: conn} do +      user = insert(:user) +      token = insert(:oauth_token, user: user, scopes: []) + +      response = +        conn +        |> assign(:user, user) +        |> assign(:token, token) +        |> post("/api/v1/markers", %{ +          home: %{last_read_id: "777"}, +          notifications: %{"last_read_id" => "69420"} +        }) +        |> json_response(403) + +      assert response == %{"error" => "Insufficient permissions: write:statuses."} +    end +  end +end diff --git a/test/web/mastodon_api/controllers/media_controller_test.exs b/test/web/mastodon_api/controllers/media_controller_test.exs new file mode 100644 index 000000000..042511ca4 --- /dev/null +++ b/test/web/mastodon_api/controllers/media_controller_test.exs @@ -0,0 +1,82 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.MastodonAPI.MediaControllerTest do +  use Pleroma.Web.ConnCase + +  alias Pleroma.Object +  alias Pleroma.User +  alias Pleroma.Web.ActivityPub.ActivityPub + +  setup do: oauth_access(["write:media"]) + +  describe "media upload" do +    setup do +      image = %Plug.Upload{ +        content_type: "image/jpg", +        path: Path.absname("test/fixtures/image.jpg"), +        filename: "an_image.jpg" +      } + +      [image: image] +    end + +    clear_config([:media_proxy]) +    clear_config([Pleroma.Upload]) + +    test "returns uploaded image", %{conn: conn, image: image} do +      desc = "Description of the image" + +      media = +        conn +        |> post("/api/v1/media", %{"file" => image, "description" => desc}) +        |> json_response(:ok) + +      assert media["type"] == "image" +      assert media["description"] == desc +      assert media["id"] + +      object = Object.get_by_id(media["id"]) +      assert object.data["actor"] == User.ap_id(conn.assigns[:user]) +    end +  end + +  describe "PUT /api/v1/media/:id" do +    setup %{user: actor} do +      file = %Plug.Upload{ +        content_type: "image/jpg", +        path: Path.absname("test/fixtures/image.jpg"), +        filename: "an_image.jpg" +      } + +      {:ok, %Object{} = object} = +        ActivityPub.upload( +          file, +          actor: User.ap_id(actor), +          description: "test-m" +        ) + +      [object: object] +    end + +    test "updates name of media", %{conn: conn, object: object} do +      media = +        conn +        |> put("/api/v1/media/#{object.id}", %{"description" => "test-media"}) +        |> json_response(:ok) + +      assert media["description"] == "test-media" +      assert refresh_record(object).data["name"] == "test-media" +    end + +    test "returns error when request is bad", %{conn: conn, object: object} do +      media = +        conn +        |> put("/api/v1/media/#{object.id}", %{}) +        |> json_response(400) + +      assert media == %{"error" => "bad_request"} +    end +  end +end diff --git a/test/web/mastodon_api/controllers/notification_controller_test.exs b/test/web/mastodon_api/controllers/notification_controller_test.exs new file mode 100644 index 000000000..6f0606250 --- /dev/null +++ b/test/web/mastodon_api/controllers/notification_controller_test.exs @@ -0,0 +1,490 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.MastodonAPI.NotificationControllerTest do +  use Pleroma.Web.ConnCase + +  alias Pleroma.Notification +  alias Pleroma.Repo +  alias Pleroma.User +  alias Pleroma.Web.CommonAPI + +  import Pleroma.Factory + +  test "list of notifications" do +    %{user: user, conn: conn} = oauth_access(["read:notifications"]) +    other_user = insert(:user) + +    {:ok, activity} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"}) + +    {:ok, [_notification]} = Notification.create_notifications(activity) + +    conn = +      conn +      |> assign(:user, user) +      |> get("/api/v1/notifications") + +    expected_response = +      "hi <span class=\"h-card\"><a data-user=\"#{user.id}\" class=\"u-url mention\" href=\"#{ +        user.ap_id +      }\" rel=\"ugc\">@<span>#{user.nickname}</span></a></span>" + +    assert [%{"status" => %{"content" => response}} | _rest] = json_response(conn, 200) +    assert response == expected_response +  end + +  test "getting a single notification" do +    %{user: user, conn: conn} = oauth_access(["read:notifications"]) +    other_user = insert(:user) + +    {:ok, activity} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"}) + +    {:ok, [notification]} = Notification.create_notifications(activity) + +    conn = get(conn, "/api/v1/notifications/#{notification.id}") + +    expected_response = +      "hi <span class=\"h-card\"><a data-user=\"#{user.id}\" class=\"u-url mention\" href=\"#{ +        user.ap_id +      }\" rel=\"ugc\">@<span>#{user.nickname}</span></a></span>" + +    assert %{"status" => %{"content" => response}} = json_response(conn, 200) +    assert response == expected_response +  end + +  test "dismissing a single notification" do +    %{user: user, conn: conn} = oauth_access(["write:notifications"]) +    other_user = insert(:user) + +    {:ok, activity} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"}) + +    {:ok, [notification]} = Notification.create_notifications(activity) + +    conn = +      conn +      |> assign(:user, user) +      |> post("/api/v1/notifications/dismiss", %{"id" => notification.id}) + +    assert %{} = json_response(conn, 200) +  end + +  test "clearing all notifications" do +    %{user: user, conn: conn} = oauth_access(["write:notifications", "read:notifications"]) +    other_user = insert(:user) + +    {:ok, activity} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"}) + +    {:ok, [_notification]} = Notification.create_notifications(activity) + +    ret_conn = post(conn, "/api/v1/notifications/clear") + +    assert %{} = json_response(ret_conn, 200) + +    ret_conn = get(conn, "/api/v1/notifications") + +    assert all = json_response(ret_conn, 200) +    assert all == [] +  end + +  test "paginates notifications using min_id, since_id, max_id, and limit" do +    %{user: user, conn: conn} = oauth_access(["read:notifications"]) +    other_user = insert(:user) + +    {:ok, activity1} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"}) +    {:ok, activity2} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"}) +    {:ok, activity3} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"}) +    {:ok, activity4} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"}) + +    notification1_id = get_notification_id_by_activity(activity1) +    notification2_id = get_notification_id_by_activity(activity2) +    notification3_id = get_notification_id_by_activity(activity3) +    notification4_id = get_notification_id_by_activity(activity4) + +    conn = assign(conn, :user, user) + +    # min_id +    result = +      conn +      |> get("/api/v1/notifications?limit=2&min_id=#{notification1_id}") +      |> json_response(:ok) + +    assert [%{"id" => ^notification3_id}, %{"id" => ^notification2_id}] = result + +    # since_id +    result = +      conn +      |> get("/api/v1/notifications?limit=2&since_id=#{notification1_id}") +      |> json_response(:ok) + +    assert [%{"id" => ^notification4_id}, %{"id" => ^notification3_id}] = result + +    # max_id +    result = +      conn +      |> get("/api/v1/notifications?limit=2&max_id=#{notification4_id}") +      |> json_response(:ok) + +    assert [%{"id" => ^notification3_id}, %{"id" => ^notification2_id}] = result +  end + +  describe "exclude_visibilities" do +    test "filters notifications for mentions" do +      %{user: user, conn: conn} = oauth_access(["read:notifications"]) +      other_user = insert(:user) + +      {:ok, public_activity} = +        CommonAPI.post(other_user, %{"status" => "@#{user.nickname}", "visibility" => "public"}) + +      {:ok, direct_activity} = +        CommonAPI.post(other_user, %{"status" => "@#{user.nickname}", "visibility" => "direct"}) + +      {:ok, unlisted_activity} = +        CommonAPI.post(other_user, %{"status" => "@#{user.nickname}", "visibility" => "unlisted"}) + +      {:ok, private_activity} = +        CommonAPI.post(other_user, %{"status" => "@#{user.nickname}", "visibility" => "private"}) + +      conn_res = +        get(conn, "/api/v1/notifications", %{ +          exclude_visibilities: ["public", "unlisted", "private"] +        }) + +      assert [%{"status" => %{"id" => id}}] = json_response(conn_res, 200) +      assert id == direct_activity.id + +      conn_res = +        get(conn, "/api/v1/notifications", %{ +          exclude_visibilities: ["public", "unlisted", "direct"] +        }) + +      assert [%{"status" => %{"id" => id}}] = json_response(conn_res, 200) +      assert id == private_activity.id + +      conn_res = +        get(conn, "/api/v1/notifications", %{ +          exclude_visibilities: ["public", "private", "direct"] +        }) + +      assert [%{"status" => %{"id" => id}}] = json_response(conn_res, 200) +      assert id == unlisted_activity.id + +      conn_res = +        get(conn, "/api/v1/notifications", %{ +          exclude_visibilities: ["unlisted", "private", "direct"] +        }) + +      assert [%{"status" => %{"id" => id}}] = json_response(conn_res, 200) +      assert id == public_activity.id +    end + +    test "filters notifications for Like activities" do +      user = insert(:user) +      %{user: other_user, conn: conn} = oauth_access(["read:notifications"]) + +      {:ok, public_activity} = +        CommonAPI.post(other_user, %{"status" => ".", "visibility" => "public"}) + +      {:ok, direct_activity} = +        CommonAPI.post(other_user, %{"status" => "@#{user.nickname}", "visibility" => "direct"}) + +      {:ok, unlisted_activity} = +        CommonAPI.post(other_user, %{"status" => ".", "visibility" => "unlisted"}) + +      {:ok, private_activity} = +        CommonAPI.post(other_user, %{"status" => ".", "visibility" => "private"}) + +      {:ok, _, _} = CommonAPI.favorite(public_activity.id, user) +      {:ok, _, _} = CommonAPI.favorite(direct_activity.id, user) +      {:ok, _, _} = CommonAPI.favorite(unlisted_activity.id, user) +      {:ok, _, _} = CommonAPI.favorite(private_activity.id, user) + +      activity_ids = +        conn +        |> get("/api/v1/notifications", %{exclude_visibilities: ["direct"]}) +        |> json_response(200) +        |> Enum.map(& &1["status"]["id"]) + +      assert public_activity.id in activity_ids +      assert unlisted_activity.id in activity_ids +      assert private_activity.id in activity_ids +      refute direct_activity.id in activity_ids + +      activity_ids = +        conn +        |> get("/api/v1/notifications", %{exclude_visibilities: ["unlisted"]}) +        |> json_response(200) +        |> Enum.map(& &1["status"]["id"]) + +      assert public_activity.id in activity_ids +      refute unlisted_activity.id in activity_ids +      assert private_activity.id in activity_ids +      assert direct_activity.id in activity_ids + +      activity_ids = +        conn +        |> get("/api/v1/notifications", %{exclude_visibilities: ["private"]}) +        |> json_response(200) +        |> Enum.map(& &1["status"]["id"]) + +      assert public_activity.id in activity_ids +      assert unlisted_activity.id in activity_ids +      refute private_activity.id in activity_ids +      assert direct_activity.id in activity_ids + +      activity_ids = +        conn +        |> get("/api/v1/notifications", %{exclude_visibilities: ["public"]}) +        |> json_response(200) +        |> Enum.map(& &1["status"]["id"]) + +      refute public_activity.id in activity_ids +      assert unlisted_activity.id in activity_ids +      assert private_activity.id in activity_ids +      assert direct_activity.id in activity_ids +    end + +    test "filters notifications for Announce activities" do +      user = insert(:user) +      %{user: other_user, conn: conn} = oauth_access(["read:notifications"]) + +      {:ok, public_activity} = +        CommonAPI.post(other_user, %{"status" => ".", "visibility" => "public"}) + +      {:ok, unlisted_activity} = +        CommonAPI.post(other_user, %{"status" => ".", "visibility" => "unlisted"}) + +      {:ok, _, _} = CommonAPI.repeat(public_activity.id, user) +      {:ok, _, _} = CommonAPI.repeat(unlisted_activity.id, user) + +      activity_ids = +        conn +        |> get("/api/v1/notifications", %{exclude_visibilities: ["unlisted"]}) +        |> json_response(200) +        |> Enum.map(& &1["status"]["id"]) + +      assert public_activity.id in activity_ids +      refute unlisted_activity.id in activity_ids +    end +  end + +  test "filters notifications using exclude_types" do +    %{user: user, conn: conn} = oauth_access(["read:notifications"]) +    other_user = insert(:user) + +    {:ok, mention_activity} = CommonAPI.post(other_user, %{"status" => "hey @#{user.nickname}"}) +    {:ok, create_activity} = CommonAPI.post(user, %{"status" => "hey"}) +    {:ok, favorite_activity, _} = CommonAPI.favorite(create_activity.id, other_user) +    {:ok, reblog_activity, _} = CommonAPI.repeat(create_activity.id, other_user) +    {:ok, _, _, follow_activity} = CommonAPI.follow(other_user, user) + +    mention_notification_id = get_notification_id_by_activity(mention_activity) +    favorite_notification_id = get_notification_id_by_activity(favorite_activity) +    reblog_notification_id = get_notification_id_by_activity(reblog_activity) +    follow_notification_id = get_notification_id_by_activity(follow_activity) + +    conn_res = +      get(conn, "/api/v1/notifications", %{exclude_types: ["mention", "favourite", "reblog"]}) + +    assert [%{"id" => ^follow_notification_id}] = json_response(conn_res, 200) + +    conn_res = +      get(conn, "/api/v1/notifications", %{exclude_types: ["favourite", "reblog", "follow"]}) + +    assert [%{"id" => ^mention_notification_id}] = json_response(conn_res, 200) + +    conn_res = +      get(conn, "/api/v1/notifications", %{exclude_types: ["reblog", "follow", "mention"]}) + +    assert [%{"id" => ^favorite_notification_id}] = json_response(conn_res, 200) + +    conn_res = +      get(conn, "/api/v1/notifications", %{exclude_types: ["follow", "mention", "favourite"]}) + +    assert [%{"id" => ^reblog_notification_id}] = json_response(conn_res, 200) +  end + +  test "destroy multiple" do +    %{user: user, conn: conn} = oauth_access(["read:notifications", "write:notifications"]) +    other_user = insert(:user) + +    {:ok, activity1} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"}) +    {:ok, activity2} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"}) +    {:ok, activity3} = CommonAPI.post(user, %{"status" => "hi @#{other_user.nickname}"}) +    {:ok, activity4} = CommonAPI.post(user, %{"status" => "hi @#{other_user.nickname}"}) + +    notification1_id = get_notification_id_by_activity(activity1) +    notification2_id = get_notification_id_by_activity(activity2) +    notification3_id = get_notification_id_by_activity(activity3) +    notification4_id = get_notification_id_by_activity(activity4) + +    result = +      conn +      |> get("/api/v1/notifications") +      |> json_response(:ok) + +    assert [%{"id" => ^notification2_id}, %{"id" => ^notification1_id}] = result + +    conn2 = +      conn +      |> assign(:user, other_user) +      |> assign(:token, insert(:oauth_token, user: other_user, scopes: ["read:notifications"])) + +    result = +      conn2 +      |> get("/api/v1/notifications") +      |> json_response(:ok) + +    assert [%{"id" => ^notification4_id}, %{"id" => ^notification3_id}] = result + +    conn_destroy = +      conn +      |> delete("/api/v1/notifications/destroy_multiple", %{ +        "ids" => [notification1_id, notification2_id] +      }) + +    assert json_response(conn_destroy, 200) == %{} + +    result = +      conn2 +      |> get("/api/v1/notifications") +      |> json_response(:ok) + +    assert [%{"id" => ^notification4_id}, %{"id" => ^notification3_id}] = result +  end + +  test "doesn't see notifications after muting user with notifications" do +    %{user: user, conn: conn} = oauth_access(["read:notifications"]) +    user2 = insert(:user) + +    {:ok, _, _, _} = CommonAPI.follow(user, user2) +    {:ok, _} = CommonAPI.post(user2, %{"status" => "hey @#{user.nickname}"}) + +    ret_conn = get(conn, "/api/v1/notifications") + +    assert length(json_response(ret_conn, 200)) == 1 + +    {:ok, _user_relationships} = User.mute(user, user2) + +    conn = get(conn, "/api/v1/notifications") + +    assert json_response(conn, 200) == [] +  end + +  test "see notifications after muting user without notifications" do +    %{user: user, conn: conn} = oauth_access(["read:notifications"]) +    user2 = insert(:user) + +    {:ok, _, _, _} = CommonAPI.follow(user, user2) +    {:ok, _} = CommonAPI.post(user2, %{"status" => "hey @#{user.nickname}"}) + +    ret_conn = get(conn, "/api/v1/notifications") + +    assert length(json_response(ret_conn, 200)) == 1 + +    {:ok, _user_relationships} = User.mute(user, user2, false) + +    conn = get(conn, "/api/v1/notifications") + +    assert length(json_response(conn, 200)) == 1 +  end + +  test "see notifications after muting user with notifications and with_muted parameter" do +    %{user: user, conn: conn} = oauth_access(["read:notifications"]) +    user2 = insert(:user) + +    {:ok, _, _, _} = CommonAPI.follow(user, user2) +    {:ok, _} = CommonAPI.post(user2, %{"status" => "hey @#{user.nickname}"}) + +    ret_conn = get(conn, "/api/v1/notifications") + +    assert length(json_response(ret_conn, 200)) == 1 + +    {:ok, _user_relationships} = User.mute(user, user2) + +    conn = get(conn, "/api/v1/notifications", %{"with_muted" => "true"}) + +    assert length(json_response(conn, 200)) == 1 +  end + +  test "see move notifications with `with_move` parameter" do +    old_user = insert(:user) +    new_user = insert(:user, also_known_as: [old_user.ap_id]) +    %{user: follower, conn: conn} = oauth_access(["read:notifications"]) + +    User.follow(follower, old_user) +    Pleroma.Web.ActivityPub.ActivityPub.move(old_user, new_user) +    Pleroma.Tests.ObanHelpers.perform_all() + +    ret_conn = get(conn, "/api/v1/notifications") + +    assert json_response(ret_conn, 200) == [] + +    conn = get(conn, "/api/v1/notifications", %{"with_move" => "true"}) + +    assert length(json_response(conn, 200)) == 1 +  end + +  describe "link headers" do +    test "preserves parameters in link headers" do +      %{user: user, conn: conn} = oauth_access(["read:notifications"]) +      other_user = insert(:user) + +      {:ok, activity1} = +        CommonAPI.post(other_user, %{ +          "status" => "hi @#{user.nickname}", +          "visibility" => "public" +        }) + +      {:ok, activity2} = +        CommonAPI.post(other_user, %{ +          "status" => "hi @#{user.nickname}", +          "visibility" => "public" +        }) + +      notification1 = Repo.get_by(Notification, activity_id: activity1.id) +      notification2 = Repo.get_by(Notification, activity_id: activity2.id) + +      conn = +        conn +        |> assign(:user, user) +        |> get("/api/v1/notifications", %{media_only: true}) + +      assert [link_header] = get_resp_header(conn, "link") +      assert link_header =~ ~r/media_only=true/ +      assert link_header =~ ~r/min_id=#{notification2.id}/ +      assert link_header =~ ~r/max_id=#{notification1.id}/ +    end +  end + +  describe "from specified user" do +    test "account_id" do +      %{user: user, conn: conn} = oauth_access(["read:notifications"]) + +      %{id: account_id} = other_user1 = insert(:user) +      other_user2 = insert(:user) + +      {:ok, _activity} = CommonAPI.post(other_user1, %{"status" => "hi @#{user.nickname}"}) +      {:ok, _activity} = CommonAPI.post(other_user2, %{"status" => "bye @#{user.nickname}"}) + +      assert [%{"account" => %{"id" => ^account_id}}] = +               conn +               |> assign(:user, user) +               |> get("/api/v1/notifications", %{account_id: account_id}) +               |> json_response(200) + +      assert %{"error" => "Account is not found"} = +               conn +               |> assign(:user, user) +               |> get("/api/v1/notifications", %{account_id: "cofe"}) +               |> json_response(404) +    end +  end + +  defp get_notification_id_by_activity(%{id: id}) do +    Notification +    |> Repo.get_by(activity_id: id) +    |> Map.get(:id) +    |> to_string() +  end +end diff --git a/test/web/mastodon_api/controllers/poll_controller_test.exs b/test/web/mastodon_api/controllers/poll_controller_test.exs new file mode 100644 index 000000000..5a1cea11b --- /dev/null +++ b/test/web/mastodon_api/controllers/poll_controller_test.exs @@ -0,0 +1,157 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.MastodonAPI.PollControllerTest do +  use Pleroma.Web.ConnCase + +  alias Pleroma.Object +  alias Pleroma.Web.CommonAPI + +  import Pleroma.Factory + +  describe "GET /api/v1/polls/:id" do +    setup do: oauth_access(["read:statuses"]) + +    test "returns poll entity for object id", %{user: user, conn: conn} do +      {:ok, activity} = +        CommonAPI.post(user, %{ +          "status" => "Pleroma does", +          "poll" => %{"options" => ["what Mastodon't", "n't what Mastodoes"], "expires_in" => 20} +        }) + +      object = Object.normalize(activity) + +      conn = get(conn, "/api/v1/polls/#{object.id}") + +      response = json_response(conn, 200) +      id = to_string(object.id) +      assert %{"id" => ^id, "expired" => false, "multiple" => false} = response +    end + +    test "does not expose polls for private statuses", %{conn: conn} do +      other_user = insert(:user) + +      {:ok, activity} = +        CommonAPI.post(other_user, %{ +          "status" => "Pleroma does", +          "poll" => %{"options" => ["what Mastodon't", "n't what Mastodoes"], "expires_in" => 20}, +          "visibility" => "private" +        }) + +      object = Object.normalize(activity) + +      conn = get(conn, "/api/v1/polls/#{object.id}") + +      assert json_response(conn, 404) +    end +  end + +  describe "POST /api/v1/polls/:id/votes" do +    setup do: oauth_access(["write:statuses"]) + +    test "votes are added to the poll", %{conn: conn} do +      other_user = insert(:user) + +      {:ok, activity} = +        CommonAPI.post(other_user, %{ +          "status" => "A very delicious sandwich", +          "poll" => %{ +            "options" => ["Lettuce", "Grilled Bacon", "Tomato"], +            "expires_in" => 20, +            "multiple" => true +          } +        }) + +      object = Object.normalize(activity) + +      conn = post(conn, "/api/v1/polls/#{object.id}/votes", %{"choices" => [0, 1, 2]}) + +      assert json_response(conn, 200) +      object = Object.get_by_id(object.id) + +      assert Enum.all?(object.data["anyOf"], fn %{"replies" => %{"totalItems" => total_items}} -> +               total_items == 1 +             end) +    end + +    test "author can't vote", %{user: user, conn: conn} do +      {:ok, activity} = +        CommonAPI.post(user, %{ +          "status" => "Am I cute?", +          "poll" => %{"options" => ["Yes", "No"], "expires_in" => 20} +        }) + +      object = Object.normalize(activity) + +      assert conn +             |> post("/api/v1/polls/#{object.id}/votes", %{"choices" => [1]}) +             |> json_response(422) == %{"error" => "Poll's author can't vote"} + +      object = Object.get_by_id(object.id) + +      refute Enum.at(object.data["oneOf"], 1)["replies"]["totalItems"] == 1 +    end + +    test "does not allow multiple choices on a single-choice question", %{conn: conn} do +      other_user = insert(:user) + +      {:ok, activity} = +        CommonAPI.post(other_user, %{ +          "status" => "The glass is", +          "poll" => %{"options" => ["half empty", "half full"], "expires_in" => 20} +        }) + +      object = Object.normalize(activity) + +      assert conn +             |> post("/api/v1/polls/#{object.id}/votes", %{"choices" => [0, 1]}) +             |> json_response(422) == %{"error" => "Too many choices"} + +      object = Object.get_by_id(object.id) + +      refute Enum.any?(object.data["oneOf"], fn %{"replies" => %{"totalItems" => total_items}} -> +               total_items == 1 +             end) +    end + +    test "does not allow choice index to be greater than options count", %{conn: conn} do +      other_user = insert(:user) + +      {:ok, activity} = +        CommonAPI.post(other_user, %{ +          "status" => "Am I cute?", +          "poll" => %{"options" => ["Yes", "No"], "expires_in" => 20} +        }) + +      object = Object.normalize(activity) + +      conn = post(conn, "/api/v1/polls/#{object.id}/votes", %{"choices" => [2]}) + +      assert json_response(conn, 422) == %{"error" => "Invalid indices"} +    end + +    test "returns 404 error when object is not exist", %{conn: conn} do +      conn = post(conn, "/api/v1/polls/1/votes", %{"choices" => [0]}) + +      assert json_response(conn, 404) == %{"error" => "Record not found"} +    end + +    test "returns 404 when poll is private and not available for user", %{conn: conn} do +      other_user = insert(:user) + +      {:ok, activity} = +        CommonAPI.post(other_user, %{ +          "status" => "Am I cute?", +          "poll" => %{"options" => ["Yes", "No"], "expires_in" => 20}, +          "visibility" => "private" +        }) + +      object = Object.normalize(activity) + +      conn = post(conn, "/api/v1/polls/#{object.id}/votes", %{"choices" => [0]}) + +      assert json_response(conn, 404) == %{"error" => "Record not found"} +    end +  end +end diff --git a/test/web/mastodon_api/controllers/report_controller_test.exs b/test/web/mastodon_api/controllers/report_controller_test.exs new file mode 100644 index 000000000..53c132ff4 --- /dev/null +++ b/test/web/mastodon_api/controllers/report_controller_test.exs @@ -0,0 +1,78 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.MastodonAPI.ReportControllerTest do +  use Pleroma.Web.ConnCase + +  alias Pleroma.Web.CommonAPI + +  import Pleroma.Factory + +  setup do: oauth_access(["write:reports"]) + +  setup do +    target_user = insert(:user) + +    {:ok, activity} = CommonAPI.post(target_user, %{"status" => "foobar"}) + +    [target_user: target_user, activity: activity] +  end + +  test "submit a basic report", %{conn: conn, target_user: target_user} do +    assert %{"action_taken" => false, "id" => _} = +             conn +             |> post("/api/v1/reports", %{"account_id" => target_user.id}) +             |> json_response(200) +  end + +  test "submit a report with statuses and comment", %{ +    conn: conn, +    target_user: target_user, +    activity: activity +  } do +    assert %{"action_taken" => false, "id" => _} = +             conn +             |> post("/api/v1/reports", %{ +               "account_id" => target_user.id, +               "status_ids" => [activity.id], +               "comment" => "bad status!", +               "forward" => "false" +             }) +             |> json_response(200) +  end + +  test "account_id is required", %{ +    conn: conn, +    activity: activity +  } do +    assert %{"error" => "Valid `account_id` required"} = +             conn +             |> post("/api/v1/reports", %{"status_ids" => [activity.id]}) +             |> json_response(400) +  end + +  test "comment must be up to the size specified in the config", %{ +    conn: conn, +    target_user: target_user +  } do +    max_size = Pleroma.Config.get([:instance, :max_report_comment_size], 1000) +    comment = String.pad_trailing("a", max_size + 1, "a") + +    error = %{"error" => "Comment must be up to #{max_size} characters"} + +    assert ^error = +             conn +             |> post("/api/v1/reports", %{"account_id" => target_user.id, "comment" => comment}) +             |> json_response(400) +  end + +  test "returns error when account is not exist", %{ +    conn: conn, +    activity: activity +  } do +    conn = post(conn, "/api/v1/reports", %{"status_ids" => [activity.id], "account_id" => "foo"}) + +    assert json_response(conn, 400) == %{"error" => "Account not found"} +  end +end diff --git a/test/web/mastodon_api/controllers/scheduled_activity_controller_test.exs b/test/web/mastodon_api/controllers/scheduled_activity_controller_test.exs new file mode 100644 index 000000000..9666a7f2e --- /dev/null +++ b/test/web/mastodon_api/controllers/scheduled_activity_controller_test.exs @@ -0,0 +1,93 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.MastodonAPI.ScheduledActivityControllerTest do +  use Pleroma.Web.ConnCase + +  alias Pleroma.Repo +  alias Pleroma.ScheduledActivity + +  import Pleroma.Factory + +  test "shows scheduled activities" do +    %{user: user, conn: conn} = oauth_access(["read:statuses"]) + +    scheduled_activity_id1 = insert(:scheduled_activity, user: user).id |> to_string() +    scheduled_activity_id2 = insert(:scheduled_activity, user: user).id |> to_string() +    scheduled_activity_id3 = insert(:scheduled_activity, user: user).id |> to_string() +    scheduled_activity_id4 = insert(:scheduled_activity, user: user).id |> to_string() + +    # min_id +    conn_res = get(conn, "/api/v1/scheduled_statuses?limit=2&min_id=#{scheduled_activity_id1}") + +    result = json_response(conn_res, 200) +    assert [%{"id" => ^scheduled_activity_id3}, %{"id" => ^scheduled_activity_id2}] = result + +    # since_id +    conn_res = get(conn, "/api/v1/scheduled_statuses?limit=2&since_id=#{scheduled_activity_id1}") + +    result = json_response(conn_res, 200) +    assert [%{"id" => ^scheduled_activity_id4}, %{"id" => ^scheduled_activity_id3}] = result + +    # max_id +    conn_res = get(conn, "/api/v1/scheduled_statuses?limit=2&max_id=#{scheduled_activity_id4}") + +    result = json_response(conn_res, 200) +    assert [%{"id" => ^scheduled_activity_id3}, %{"id" => ^scheduled_activity_id2}] = result +  end + +  test "shows a scheduled activity" do +    %{user: user, conn: conn} = oauth_access(["read:statuses"]) +    scheduled_activity = insert(:scheduled_activity, user: user) + +    res_conn = get(conn, "/api/v1/scheduled_statuses/#{scheduled_activity.id}") + +    assert %{"id" => scheduled_activity_id} = json_response(res_conn, 200) +    assert scheduled_activity_id == scheduled_activity.id |> to_string() + +    res_conn = get(conn, "/api/v1/scheduled_statuses/404") + +    assert %{"error" => "Record not found"} = json_response(res_conn, 404) +  end + +  test "updates a scheduled activity" do +    %{user: user, conn: conn} = oauth_access(["write:statuses"]) +    scheduled_activity = insert(:scheduled_activity, user: user) + +    new_scheduled_at = +      NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(120), :millisecond) + +    res_conn = +      put(conn, "/api/v1/scheduled_statuses/#{scheduled_activity.id}", %{ +        scheduled_at: new_scheduled_at +      }) + +    assert %{"scheduled_at" => expected_scheduled_at} = json_response(res_conn, 200) +    assert expected_scheduled_at == Pleroma.Web.CommonAPI.Utils.to_masto_date(new_scheduled_at) + +    res_conn = put(conn, "/api/v1/scheduled_statuses/404", %{scheduled_at: new_scheduled_at}) + +    assert %{"error" => "Record not found"} = json_response(res_conn, 404) +  end + +  test "deletes a scheduled activity" do +    %{user: user, conn: conn} = oauth_access(["write:statuses"]) +    scheduled_activity = insert(:scheduled_activity, user: user) + +    res_conn = +      conn +      |> assign(:user, user) +      |> delete("/api/v1/scheduled_statuses/#{scheduled_activity.id}") + +    assert %{} = json_response(res_conn, 200) +    assert nil == Repo.get(ScheduledActivity, scheduled_activity.id) + +    res_conn = +      conn +      |> assign(:user, user) +      |> delete("/api/v1/scheduled_statuses/#{scheduled_activity.id}") + +    assert %{"error" => "Record not found"} = json_response(res_conn, 404) +  end +end diff --git a/test/web/mastodon_api/search_controller_test.exs b/test/web/mastodon_api/controllers/search_controller_test.exs index 043b96c14..effae130c 100644 --- a/test/web/mastodon_api/search_controller_test.exs +++ b/test/web/mastodon_api/controllers/search_controller_test.exs @@ -42,7 +42,7 @@ defmodule Pleroma.Web.MastodonAPI.SearchControllerTest do        user_two = insert(:user, %{nickname: "shp@shitposter.club"})        user_three = insert(:user, %{nickname: "shp@heldscal.la", name: "I love 2hu"}) -      {:ok, activity} = CommonAPI.post(user, %{"status" => "This is about 2hu private"}) +      {:ok, activity} = CommonAPI.post(user, %{"status" => "This is about 2hu private 天子"})        {:ok, _activity} =          CommonAPI.post(user, %{ @@ -52,9 +52,10 @@ defmodule Pleroma.Web.MastodonAPI.SearchControllerTest do        {:ok, _} = CommonAPI.post(user_two, %{"status" => "This isn't"}) -      conn = get(conn, "/api/v2/search", %{"q" => "2hu #private"}) - -      assert results = json_response(conn, 200) +      results = +        conn +        |> get("/api/v2/search", %{"q" => "2hu #private"}) +        |> json_response(200)        [account | _] = results["accounts"]        assert account["id"] == to_string(user_three.id) @@ -65,18 +66,47 @@ defmodule Pleroma.Web.MastodonAPI.SearchControllerTest do        [status] = results["statuses"]        assert status["id"] == to_string(activity.id) + +      results = +        get(conn, "/api/v2/search", %{"q" => "天子"}) +        |> json_response(200) + +      [status] = results["statuses"] +      assert status["id"] == to_string(activity.id) +    end + +    test "excludes a blocked users from search results", %{conn: conn} do +      user = insert(:user) +      user_smith = insert(:user, %{nickname: "Agent", name: "I love 2hu"}) +      user_neo = insert(:user, %{nickname: "Agent Neo", name: "Agent"}) + +      {:ok, act1} = CommonAPI.post(user, %{"status" => "This is about 2hu private 天子"}) +      {:ok, act2} = CommonAPI.post(user_smith, %{"status" => "Agent Smith"}) +      {:ok, act3} = CommonAPI.post(user_neo, %{"status" => "Agent Smith"}) +      Pleroma.User.block(user, user_smith) + +      results = +        conn +        |> assign(:user, user) +        |> assign(:token, insert(:oauth_token, user: user, scopes: ["read"])) +        |> get("/api/v2/search", %{"q" => "Agent"}) +        |> json_response(200) + +      status_ids = Enum.map(results["statuses"], fn g -> g["id"] end) + +      assert act3.id in status_ids +      refute act2.id in status_ids +      refute act1.id in status_ids      end    end    describe ".account_search" do      test "account search", %{conn: conn} do -      user = insert(:user)        user_two = insert(:user, %{nickname: "shp@shitposter.club"})        user_three = insert(:user, %{nickname: "shp@heldscal.la", name: "I love 2hu"})        results =          conn -        |> assign(:user, user)          |> get("/api/v1/accounts/search", %{"q" => "shp"})          |> json_response(200) @@ -87,7 +117,6 @@ defmodule Pleroma.Web.MastodonAPI.SearchControllerTest do        results =          conn -        |> assign(:user, user)          |> get("/api/v1/accounts/search", %{"q" => "2hu"})          |> json_response(200) @@ -95,6 +124,17 @@ defmodule Pleroma.Web.MastodonAPI.SearchControllerTest do        assert user_three.nickname in result_ids      end + +    test "returns account if query contains a space", %{conn: conn} do +      insert(:user, %{nickname: "shp@shitposter.club"}) + +      results = +        conn +        |> get("/api/v1/accounts/search", %{"q" => "shp@shitposter.club xxx "}) +        |> json_response(200) + +      assert length(results) == 1 +    end    end    describe ".search" do @@ -131,11 +171,10 @@ defmodule Pleroma.Web.MastodonAPI.SearchControllerTest do        {:ok, _} = CommonAPI.post(user_two, %{"status" => "This isn't"}) -      conn = +      results =          conn          |> get("/api/v1/search", %{"q" => "2hu"}) - -      assert results = json_response(conn, 200) +        |> json_response(200)        [account | _] = results["accounts"]        assert account["id"] == to_string(user_three.id) @@ -146,15 +185,19 @@ defmodule Pleroma.Web.MastodonAPI.SearchControllerTest do        assert status["id"] == to_string(activity.id)      end -    test "search fetches remote statuses", %{conn: conn} do +    test "search fetches remote statuses and prefers them over other results", %{conn: conn} do        capture_log(fn -> -        conn = +        {:ok, %{id: activity_id}} = +          CommonAPI.post(insert(:user), %{ +            "status" => "check out https://shitposter.club/notice/2827873" +          }) + +        results =            conn            |> get("/api/v1/search", %{"q" => "https://shitposter.club/notice/2827873"}) +          |> json_response(200) -        assert results = json_response(conn, 200) - -        [status] = results["statuses"] +        [status, %{"id" => ^activity_id}] = results["statuses"]          assert status["uri"] ==                   "tag:shitposter.club,2017-05-05:noticeId=2827873:objectType=comment" @@ -169,11 +212,10 @@ defmodule Pleroma.Web.MastodonAPI.SearchControllerTest do          })        capture_log(fn -> -        conn = +        results =            conn            |> get("/api/v1/search", %{"q" => Object.normalize(activity).data["id"]}) - -        assert results = json_response(conn, 200) +          |> json_response(200)          [] = results["statuses"]        end) @@ -182,22 +224,23 @@ defmodule Pleroma.Web.MastodonAPI.SearchControllerTest do      test "search fetches remote accounts", %{conn: conn} do        user = insert(:user) -      conn = +      results =          conn          |> assign(:user, user) -        |> get("/api/v1/search", %{"q" => "shp@social.heldscal.la", "resolve" => "true"}) +        |> assign(:token, insert(:oauth_token, user: user, scopes: ["read"])) +        |> get("/api/v1/search", %{"q" => "mike@osada.macgirvin.com", "resolve" => "true"}) +        |> json_response(200) -      assert results = json_response(conn, 200)        [account] = results["accounts"] -      assert account["acct"] == "shp@social.heldscal.la" +      assert account["acct"] == "mike@osada.macgirvin.com"      end      test "search doesn't fetch remote accounts if resolve is false", %{conn: conn} do -      conn = +      results =          conn -        |> get("/api/v1/search", %{"q" => "shp@social.heldscal.la", "resolve" => "false"}) +        |> get("/api/v1/search", %{"q" => "mike@osada.macgirvin.com", "resolve" => "false"}) +        |> json_response(200) -      assert results = json_response(conn, 200)        assert [] == results["accounts"]      end diff --git a/test/web/mastodon_api/controllers/status_controller_test.exs b/test/web/mastodon_api/controllers/status_controller_test.exs new file mode 100644 index 000000000..b03b4b344 --- /dev/null +++ b/test/web/mastodon_api/controllers/status_controller_test.exs @@ -0,0 +1,1226 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do +  use Pleroma.Web.ConnCase + +  alias Pleroma.Activity +  alias Pleroma.ActivityExpiration +  alias Pleroma.Config +  alias Pleroma.Conversation.Participation +  alias Pleroma.Object +  alias Pleroma.Repo +  alias Pleroma.ScheduledActivity +  alias Pleroma.Tests.ObanHelpers +  alias Pleroma.User +  alias Pleroma.Web.ActivityPub.ActivityPub +  alias Pleroma.Web.CommonAPI + +  import Pleroma.Factory + +  clear_config([:instance, :federating]) +  clear_config([:instance, :allow_relay]) + +  describe "posting statuses" do +    setup do: oauth_access(["write:statuses"]) + +    test "posting a status does not increment reblog_count when relaying", %{conn: conn} do +      Pleroma.Config.put([:instance, :federating], true) +      Pleroma.Config.get([:instance, :allow_relay], true) + +      response = +        conn +        |> post("api/v1/statuses", %{ +          "content_type" => "text/plain", +          "source" => "Pleroma FE", +          "status" => "Hello world", +          "visibility" => "public" +        }) +        |> json_response(200) + +      assert response["reblogs_count"] == 0 +      ObanHelpers.perform_all() + +      response = +        conn +        |> get("api/v1/statuses/#{response["id"]}", %{}) +        |> json_response(200) + +      assert response["reblogs_count"] == 0 +    end + +    test "posting a status", %{conn: conn} do +      idempotency_key = "Pikachu rocks!" + +      conn_one = +        conn +        |> put_req_header("idempotency-key", idempotency_key) +        |> post("/api/v1/statuses", %{ +          "status" => "cofe", +          "spoiler_text" => "2hu", +          "sensitive" => "false" +        }) + +      {:ok, ttl} = Cachex.ttl(:idempotency_cache, idempotency_key) +      # Six hours +      assert ttl > :timer.seconds(6 * 60 * 60 - 1) + +      assert %{"content" => "cofe", "id" => id, "spoiler_text" => "2hu", "sensitive" => false} = +               json_response(conn_one, 200) + +      assert Activity.get_by_id(id) + +      conn_two = +        conn +        |> put_req_header("idempotency-key", idempotency_key) +        |> post("/api/v1/statuses", %{ +          "status" => "cofe", +          "spoiler_text" => "2hu", +          "sensitive" => "false" +        }) + +      assert %{"id" => second_id} = json_response(conn_two, 200) +      assert id == second_id + +      conn_three = +        conn +        |> post("/api/v1/statuses", %{ +          "status" => "cofe", +          "spoiler_text" => "2hu", +          "sensitive" => "false" +        }) + +      assert %{"id" => third_id} = json_response(conn_three, 200) +      refute id == third_id + +      # An activity that will expire: +      # 2 hours +      expires_in = 120 * 60 + +      conn_four = +        conn +        |> post("api/v1/statuses", %{ +          "status" => "oolong", +          "expires_in" => expires_in +        }) + +      assert fourth_response = %{"id" => fourth_id} = json_response(conn_four, 200) +      assert activity = Activity.get_by_id(fourth_id) +      assert expiration = ActivityExpiration.get_by_activity_id(fourth_id) + +      estimated_expires_at = +        NaiveDateTime.utc_now() +        |> NaiveDateTime.add(expires_in) +        |> NaiveDateTime.truncate(:second) + +      # This assert will fail if the test takes longer than a minute. I sure hope it never does: +      assert abs(NaiveDateTime.diff(expiration.scheduled_at, estimated_expires_at, :second)) < 60 + +      assert fourth_response["pleroma"]["expires_at"] == +               NaiveDateTime.to_iso8601(expiration.scheduled_at) +    end + +    test "posting an undefined status with an attachment", %{user: user, conn: conn} do +      file = %Plug.Upload{ +        content_type: "image/jpg", +        path: Path.absname("test/fixtures/image.jpg"), +        filename: "an_image.jpg" +      } + +      {:ok, upload} = ActivityPub.upload(file, actor: user.ap_id) + +      conn = +        post(conn, "/api/v1/statuses", %{ +          "media_ids" => [to_string(upload.id)] +        }) + +      assert json_response(conn, 200) +    end + +    test "replying to a status", %{user: user, conn: conn} do +      {:ok, replied_to} = CommonAPI.post(user, %{"status" => "cofe"}) + +      conn = +        conn +        |> post("/api/v1/statuses", %{"status" => "xD", "in_reply_to_id" => replied_to.id}) + +      assert %{"content" => "xD", "id" => id} = json_response(conn, 200) + +      activity = Activity.get_by_id(id) + +      assert activity.data["context"] == replied_to.data["context"] +      assert Activity.get_in_reply_to_activity(activity).id == replied_to.id +    end + +    test "replying to a direct message with visibility other than direct", %{ +      user: user, +      conn: conn +    } do +      {:ok, replied_to} = CommonAPI.post(user, %{"status" => "suya..", "visibility" => "direct"}) + +      Enum.each(["public", "private", "unlisted"], fn visibility -> +        conn = +          conn +          |> post("/api/v1/statuses", %{ +            "status" => "@#{user.nickname} hey", +            "in_reply_to_id" => replied_to.id, +            "visibility" => visibility +          }) + +        assert json_response(conn, 422) == %{"error" => "The message visibility must be direct"} +      end) +    end + +    test "posting a status with an invalid in_reply_to_id", %{conn: conn} do +      conn = post(conn, "/api/v1/statuses", %{"status" => "xD", "in_reply_to_id" => ""}) + +      assert %{"content" => "xD", "id" => id} = json_response(conn, 200) +      assert Activity.get_by_id(id) +    end + +    test "posting a sensitive status", %{conn: conn} do +      conn = post(conn, "/api/v1/statuses", %{"status" => "cofe", "sensitive" => true}) + +      assert %{"content" => "cofe", "id" => id, "sensitive" => true} = json_response(conn, 200) +      assert Activity.get_by_id(id) +    end + +    test "posting a fake status", %{conn: conn} do +      real_conn = +        post(conn, "/api/v1/statuses", %{ +          "status" => +            "\"Tenshi Eating a Corndog\" is a much discussed concept on /jp/. The significance of it is disputed, so I will focus on one core concept: the symbolism behind it" +        }) + +      real_status = json_response(real_conn, 200) + +      assert real_status +      assert Object.get_by_ap_id(real_status["uri"]) + +      real_status = +        real_status +        |> Map.put("id", nil) +        |> Map.put("url", nil) +        |> Map.put("uri", nil) +        |> Map.put("created_at", nil) +        |> Kernel.put_in(["pleroma", "conversation_id"], nil) + +      fake_conn = +        post(conn, "/api/v1/statuses", %{ +          "status" => +            "\"Tenshi Eating a Corndog\" is a much discussed concept on /jp/. The significance of it is disputed, so I will focus on one core concept: the symbolism behind it", +          "preview" => true +        }) + +      fake_status = json_response(fake_conn, 200) + +      assert fake_status +      refute Object.get_by_ap_id(fake_status["uri"]) + +      fake_status = +        fake_status +        |> Map.put("id", nil) +        |> Map.put("url", nil) +        |> Map.put("uri", nil) +        |> Map.put("created_at", nil) +        |> Kernel.put_in(["pleroma", "conversation_id"], nil) + +      assert real_status == fake_status +    end + +    test "posting a status with OGP link preview", %{conn: conn} do +      Tesla.Mock.mock(fn env -> apply(HttpRequestMock, :request, [env]) end) +      Config.put([:rich_media, :enabled], true) + +      conn = +        post(conn, "/api/v1/statuses", %{ +          "status" => "https://example.com/ogp" +        }) + +      assert %{"id" => id, "card" => %{"title" => "The Rock"}} = json_response(conn, 200) +      assert Activity.get_by_id(id) +    end + +    test "posting a direct status", %{conn: conn} do +      user2 = insert(:user) +      content = "direct cofe @#{user2.nickname}" + +      conn = post(conn, "api/v1/statuses", %{"status" => content, "visibility" => "direct"}) + +      assert %{"id" => id} = response = json_response(conn, 200) +      assert response["visibility"] == "direct" +      assert response["pleroma"]["direct_conversation_id"] +      assert activity = Activity.get_by_id(id) +      assert activity.recipients == [user2.ap_id, conn.assigns[:user].ap_id] +      assert activity.data["to"] == [user2.ap_id] +      assert activity.data["cc"] == [] +    end +  end + +  describe "posting scheduled statuses" do +    setup do: oauth_access(["write:statuses"]) + +    test "creates a scheduled activity", %{conn: conn} do +      scheduled_at = NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(120), :millisecond) + +      conn = +        post(conn, "/api/v1/statuses", %{ +          "status" => "scheduled", +          "scheduled_at" => scheduled_at +        }) + +      assert %{"scheduled_at" => expected_scheduled_at} = json_response(conn, 200) +      assert expected_scheduled_at == CommonAPI.Utils.to_masto_date(scheduled_at) +      assert [] == Repo.all(Activity) +    end + +    test "creates a scheduled activity with a media attachment", %{user: user, conn: conn} do +      scheduled_at = NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(120), :millisecond) + +      file = %Plug.Upload{ +        content_type: "image/jpg", +        path: Path.absname("test/fixtures/image.jpg"), +        filename: "an_image.jpg" +      } + +      {:ok, upload} = ActivityPub.upload(file, actor: user.ap_id) + +      conn = +        post(conn, "/api/v1/statuses", %{ +          "media_ids" => [to_string(upload.id)], +          "status" => "scheduled", +          "scheduled_at" => scheduled_at +        }) + +      assert %{"media_attachments" => [media_attachment]} = json_response(conn, 200) +      assert %{"type" => "image"} = media_attachment +    end + +    test "skips the scheduling and creates the activity if scheduled_at is earlier than 5 minutes from now", +         %{conn: conn} do +      scheduled_at = +        NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(5) - 1, :millisecond) + +      conn = +        post(conn, "/api/v1/statuses", %{ +          "status" => "not scheduled", +          "scheduled_at" => scheduled_at +        }) + +      assert %{"content" => "not scheduled"} = json_response(conn, 200) +      assert [] == Repo.all(ScheduledActivity) +    end + +    test "returns error when daily user limit is exceeded", %{user: user, conn: conn} do +      today = +        NaiveDateTime.utc_now() +        |> NaiveDateTime.add(:timer.minutes(6), :millisecond) +        |> NaiveDateTime.to_iso8601() + +      attrs = %{params: %{}, scheduled_at: today} +      {:ok, _} = ScheduledActivity.create(user, attrs) +      {:ok, _} = ScheduledActivity.create(user, attrs) + +      conn = post(conn, "/api/v1/statuses", %{"status" => "scheduled", "scheduled_at" => today}) + +      assert %{"error" => "daily limit exceeded"} == json_response(conn, 422) +    end + +    test "returns error when total user limit is exceeded", %{user: user, conn: conn} do +      today = +        NaiveDateTime.utc_now() +        |> NaiveDateTime.add(:timer.minutes(6), :millisecond) +        |> NaiveDateTime.to_iso8601() + +      tomorrow = +        NaiveDateTime.utc_now() +        |> NaiveDateTime.add(:timer.hours(36), :millisecond) +        |> NaiveDateTime.to_iso8601() + +      attrs = %{params: %{}, scheduled_at: today} +      {:ok, _} = ScheduledActivity.create(user, attrs) +      {:ok, _} = ScheduledActivity.create(user, attrs) +      {:ok, _} = ScheduledActivity.create(user, %{params: %{}, scheduled_at: tomorrow}) + +      conn = +        post(conn, "/api/v1/statuses", %{"status" => "scheduled", "scheduled_at" => tomorrow}) + +      assert %{"error" => "total limit exceeded"} == json_response(conn, 422) +    end +  end + +  describe "posting polls" do +    setup do: oauth_access(["write:statuses"]) + +    test "posting a poll", %{conn: conn} do +      time = NaiveDateTime.utc_now() + +      conn = +        post(conn, "/api/v1/statuses", %{ +          "status" => "Who is the #bestgrill?", +          "poll" => %{"options" => ["Rei", "Asuka", "Misato"], "expires_in" => 420} +        }) + +      response = json_response(conn, 200) + +      assert Enum.all?(response["poll"]["options"], fn %{"title" => title} -> +               title in ["Rei", "Asuka", "Misato"] +             end) + +      assert NaiveDateTime.diff(NaiveDateTime.from_iso8601!(response["poll"]["expires_at"]), time) in 420..430 +      refute response["poll"]["expred"] +    end + +    test "option limit is enforced", %{conn: conn} do +      limit = Config.get([:instance, :poll_limits, :max_options]) + +      conn = +        post(conn, "/api/v1/statuses", %{ +          "status" => "desu~", +          "poll" => %{"options" => Enum.map(0..limit, fn _ -> "desu" end), "expires_in" => 1} +        }) + +      %{"error" => error} = json_response(conn, 422) +      assert error == "Poll can't contain more than #{limit} options" +    end + +    test "option character limit is enforced", %{conn: conn} do +      limit = Config.get([:instance, :poll_limits, :max_option_chars]) + +      conn = +        post(conn, "/api/v1/statuses", %{ +          "status" => "...", +          "poll" => %{ +            "options" => [Enum.reduce(0..limit, "", fn _, acc -> acc <> "." end)], +            "expires_in" => 1 +          } +        }) + +      %{"error" => error} = json_response(conn, 422) +      assert error == "Poll options cannot be longer than #{limit} characters each" +    end + +    test "minimal date limit is enforced", %{conn: conn} do +      limit = Config.get([:instance, :poll_limits, :min_expiration]) + +      conn = +        post(conn, "/api/v1/statuses", %{ +          "status" => "imagine arbitrary limits", +          "poll" => %{ +            "options" => ["this post was made by pleroma gang"], +            "expires_in" => limit - 1 +          } +        }) + +      %{"error" => error} = json_response(conn, 422) +      assert error == "Expiration date is too soon" +    end + +    test "maximum date limit is enforced", %{conn: conn} do +      limit = Config.get([:instance, :poll_limits, :max_expiration]) + +      conn = +        post(conn, "/api/v1/statuses", %{ +          "status" => "imagine arbitrary limits", +          "poll" => %{ +            "options" => ["this post was made by pleroma gang"], +            "expires_in" => limit + 1 +          } +        }) + +      %{"error" => error} = json_response(conn, 422) +      assert error == "Expiration date is too far in the future" +    end +  end + +  test "get a status" do +    %{conn: conn} = oauth_access(["read:statuses"]) +    activity = insert(:note_activity) + +    conn = get(conn, "/api/v1/statuses/#{activity.id}") + +    assert %{"id" => id} = json_response(conn, 200) +    assert id == to_string(activity.id) +  end + +  test "get a direct status" do +    %{user: user, conn: conn} = oauth_access(["read:statuses"]) +    other_user = insert(:user) + +    {:ok, activity} = +      CommonAPI.post(user, %{"status" => "@#{other_user.nickname}", "visibility" => "direct"}) + +    conn = +      conn +      |> assign(:user, user) +      |> get("/api/v1/statuses/#{activity.id}") + +    [participation] = Participation.for_user(user) + +    res = json_response(conn, 200) +    assert res["pleroma"]["direct_conversation_id"] == participation.id +  end + +  test "get statuses by IDs" do +    %{conn: conn} = oauth_access(["read:statuses"]) +    %{id: id1} = insert(:note_activity) +    %{id: id2} = insert(:note_activity) + +    query_string = "ids[]=#{id1}&ids[]=#{id2}" +    conn = get(conn, "/api/v1/statuses/?#{query_string}") + +    assert [%{"id" => ^id1}, %{"id" => ^id2}] = Enum.sort_by(json_response(conn, :ok), & &1["id"]) +  end + +  describe "deleting a status" do +    test "when you created it" do +      %{user: author, conn: conn} = oauth_access(["write:statuses"]) +      activity = insert(:note_activity, user: author) + +      conn = +        conn +        |> assign(:user, author) +        |> delete("/api/v1/statuses/#{activity.id}") + +      assert %{} = json_response(conn, 200) + +      refute Activity.get_by_id(activity.id) +    end + +    test "when you didn't create it" do +      %{conn: conn} = oauth_access(["write:statuses"]) +      activity = insert(:note_activity) + +      conn = delete(conn, "/api/v1/statuses/#{activity.id}") + +      assert %{"error" => _} = json_response(conn, 403) + +      assert Activity.get_by_id(activity.id) == activity +    end + +    test "when you're an admin or moderator", %{conn: conn} do +      activity1 = insert(:note_activity) +      activity2 = insert(:note_activity) +      admin = insert(:user, is_admin: true) +      moderator = insert(:user, is_moderator: true) + +      res_conn = +        conn +        |> assign(:user, admin) +        |> assign(:token, insert(:oauth_token, user: admin, scopes: ["write:statuses"])) +        |> delete("/api/v1/statuses/#{activity1.id}") + +      assert %{} = json_response(res_conn, 200) + +      res_conn = +        conn +        |> assign(:user, moderator) +        |> assign(:token, insert(:oauth_token, user: moderator, scopes: ["write:statuses"])) +        |> delete("/api/v1/statuses/#{activity2.id}") + +      assert %{} = json_response(res_conn, 200) + +      refute Activity.get_by_id(activity1.id) +      refute Activity.get_by_id(activity2.id) +    end +  end + +  describe "reblogging" do +    setup do: oauth_access(["write:statuses"]) + +    test "reblogs and returns the reblogged status", %{conn: conn} do +      activity = insert(:note_activity) + +      conn = post(conn, "/api/v1/statuses/#{activity.id}/reblog") + +      assert %{ +               "reblog" => %{"id" => id, "reblogged" => true, "reblogs_count" => 1}, +               "reblogged" => true +             } = json_response(conn, 200) + +      assert to_string(activity.id) == id +    end + +    test "reblogs privately and returns the reblogged status", %{conn: conn} do +      activity = insert(:note_activity) + +      conn = post(conn, "/api/v1/statuses/#{activity.id}/reblog", %{"visibility" => "private"}) + +      assert %{ +               "reblog" => %{"id" => id, "reblogged" => true, "reblogs_count" => 1}, +               "reblogged" => true, +               "visibility" => "private" +             } = json_response(conn, 200) + +      assert to_string(activity.id) == id +    end + +    test "reblogged status for another user" do +      activity = insert(:note_activity) +      user1 = insert(:user) +      user2 = insert(:user) +      user3 = insert(:user) +      CommonAPI.favorite(activity.id, user2) +      {:ok, _bookmark} = Pleroma.Bookmark.create(user2.id, activity.id) +      {:ok, reblog_activity1, _object} = CommonAPI.repeat(activity.id, user1) +      {:ok, _, _object} = CommonAPI.repeat(activity.id, user2) + +      conn_res = +        build_conn() +        |> assign(:user, user3) +        |> assign(:token, insert(:oauth_token, user: user3, scopes: ["read:statuses"])) +        |> get("/api/v1/statuses/#{reblog_activity1.id}") + +      assert %{ +               "reblog" => %{"id" => id, "reblogged" => false, "reblogs_count" => 2}, +               "reblogged" => false, +               "favourited" => false, +               "bookmarked" => false +             } = json_response(conn_res, 200) + +      conn_res = +        build_conn() +        |> assign(:user, user2) +        |> assign(:token, insert(:oauth_token, user: user2, scopes: ["read:statuses"])) +        |> get("/api/v1/statuses/#{reblog_activity1.id}") + +      assert %{ +               "reblog" => %{"id" => id, "reblogged" => true, "reblogs_count" => 2}, +               "reblogged" => true, +               "favourited" => true, +               "bookmarked" => true +             } = json_response(conn_res, 200) + +      assert to_string(activity.id) == id +    end + +    test "returns 400 error when activity is not exist", %{conn: conn} do +      conn = post(conn, "/api/v1/statuses/foo/reblog") + +      assert json_response(conn, 400) == %{"error" => "Could not repeat"} +    end +  end + +  describe "unreblogging" do +    setup do: oauth_access(["write:statuses"]) + +    test "unreblogs and returns the unreblogged status", %{user: user, conn: conn} do +      activity = insert(:note_activity) + +      {:ok, _, _} = CommonAPI.repeat(activity.id, user) + +      conn = post(conn, "/api/v1/statuses/#{activity.id}/unreblog") + +      assert %{"id" => id, "reblogged" => false, "reblogs_count" => 0} = json_response(conn, 200) + +      assert to_string(activity.id) == id +    end + +    test "returns 400 error when activity is not exist", %{conn: conn} do +      conn = post(conn, "/api/v1/statuses/foo/unreblog") + +      assert json_response(conn, 400) == %{"error" => "Could not unrepeat"} +    end +  end + +  describe "favoriting" do +    setup do: oauth_access(["write:favourites"]) + +    test "favs a status and returns it", %{conn: conn} do +      activity = insert(:note_activity) + +      conn = post(conn, "/api/v1/statuses/#{activity.id}/favourite") + +      assert %{"id" => id, "favourites_count" => 1, "favourited" => true} = +               json_response(conn, 200) + +      assert to_string(activity.id) == id +    end + +    test "favoriting twice will just return 200", %{conn: conn} do +      activity = insert(:note_activity) + +      post(conn, "/api/v1/statuses/#{activity.id}/favourite") +      assert post(conn, "/api/v1/statuses/#{activity.id}/favourite") |> json_response(200) +    end + +    test "returns 400 error for a wrong id", %{conn: conn} do +      conn = post(conn, "/api/v1/statuses/1/favourite") + +      assert json_response(conn, 400) == %{"error" => "Could not favorite"} +    end +  end + +  describe "unfavoriting" do +    setup do: oauth_access(["write:favourites"]) + +    test "unfavorites a status and returns it", %{user: user, conn: conn} do +      activity = insert(:note_activity) + +      {:ok, _, _} = CommonAPI.favorite(activity.id, user) + +      conn = post(conn, "/api/v1/statuses/#{activity.id}/unfavourite") + +      assert %{"id" => id, "favourites_count" => 0, "favourited" => false} = +               json_response(conn, 200) + +      assert to_string(activity.id) == id +    end + +    test "returns 400 error for a wrong id", %{conn: conn} do +      conn = post(conn, "/api/v1/statuses/1/unfavourite") + +      assert json_response(conn, 400) == %{"error" => "Could not unfavorite"} +    end +  end + +  describe "pinned statuses" do +    setup do: oauth_access(["write:accounts"]) + +    setup %{user: user} do +      {:ok, activity} = CommonAPI.post(user, %{"status" => "HI!!!"}) + +      %{activity: activity} +    end + +    clear_config([:instance, :max_pinned_statuses]) do +      Config.put([:instance, :max_pinned_statuses], 1) +    end + +    test "pin status", %{conn: conn, user: user, activity: activity} do +      id_str = to_string(activity.id) + +      assert %{"id" => ^id_str, "pinned" => true} = +               conn +               |> post("/api/v1/statuses/#{activity.id}/pin") +               |> json_response(200) + +      assert [%{"id" => ^id_str, "pinned" => true}] = +               conn +               |> get("/api/v1/accounts/#{user.id}/statuses?pinned=true") +               |> json_response(200) +    end + +    test "/pin: returns 400 error when activity is not public", %{conn: conn, user: user} do +      {:ok, dm} = CommonAPI.post(user, %{"status" => "test", "visibility" => "direct"}) + +      conn = post(conn, "/api/v1/statuses/#{dm.id}/pin") + +      assert json_response(conn, 400) == %{"error" => "Could not pin"} +    end + +    test "unpin status", %{conn: conn, user: user, activity: activity} do +      {:ok, _} = CommonAPI.pin(activity.id, user) +      user = refresh_record(user) + +      id_str = to_string(activity.id) + +      assert %{"id" => ^id_str, "pinned" => false} = +               conn +               |> assign(:user, user) +               |> post("/api/v1/statuses/#{activity.id}/unpin") +               |> json_response(200) + +      assert [] = +               conn +               |> get("/api/v1/accounts/#{user.id}/statuses?pinned=true") +               |> json_response(200) +    end + +    test "/unpin: returns 400 error when activity is not exist", %{conn: conn} do +      conn = post(conn, "/api/v1/statuses/1/unpin") + +      assert json_response(conn, 400) == %{"error" => "Could not unpin"} +    end + +    test "max pinned statuses", %{conn: conn, user: user, activity: activity_one} do +      {:ok, activity_two} = CommonAPI.post(user, %{"status" => "HI!!!"}) + +      id_str_one = to_string(activity_one.id) + +      assert %{"id" => ^id_str_one, "pinned" => true} = +               conn +               |> post("/api/v1/statuses/#{id_str_one}/pin") +               |> json_response(200) + +      user = refresh_record(user) + +      assert %{"error" => "You have already pinned the maximum number of statuses"} = +               conn +               |> assign(:user, user) +               |> post("/api/v1/statuses/#{activity_two.id}/pin") +               |> json_response(400) +    end +  end + +  describe "cards" do +    setup do +      Config.put([:rich_media, :enabled], true) + +      oauth_access(["read:statuses"]) +    end + +    test "returns rich-media card", %{conn: conn, user: user} do +      Tesla.Mock.mock(fn env -> apply(HttpRequestMock, :request, [env]) end) + +      {:ok, activity} = CommonAPI.post(user, %{"status" => "https://example.com/ogp"}) + +      card_data = %{ +        "image" => "http://ia.media-imdb.com/images/rock.jpg", +        "provider_name" => "example.com", +        "provider_url" => "https://example.com", +        "title" => "The Rock", +        "type" => "link", +        "url" => "https://example.com/ogp", +        "description" => +          "Directed by Michael Bay. With Sean Connery, Nicolas Cage, Ed Harris, John Spencer.", +        "pleroma" => %{ +          "opengraph" => %{ +            "image" => "http://ia.media-imdb.com/images/rock.jpg", +            "title" => "The Rock", +            "type" => "video.movie", +            "url" => "https://example.com/ogp", +            "description" => +              "Directed by Michael Bay. With Sean Connery, Nicolas Cage, Ed Harris, John Spencer." +          } +        } +      } + +      response = +        conn +        |> get("/api/v1/statuses/#{activity.id}/card") +        |> json_response(200) + +      assert response == card_data + +      # works with private posts +      {:ok, activity} = +        CommonAPI.post(user, %{"status" => "https://example.com/ogp", "visibility" => "direct"}) + +      response_two = +        conn +        |> get("/api/v1/statuses/#{activity.id}/card") +        |> json_response(200) + +      assert response_two == card_data +    end + +    test "replaces missing description with an empty string", %{conn: conn, user: user} do +      Tesla.Mock.mock(fn env -> apply(HttpRequestMock, :request, [env]) end) + +      {:ok, activity} = +        CommonAPI.post(user, %{"status" => "https://example.com/ogp-missing-data"}) + +      response = +        conn +        |> get("/api/v1/statuses/#{activity.id}/card") +        |> json_response(:ok) + +      assert response == %{ +               "type" => "link", +               "title" => "Pleroma", +               "description" => "", +               "image" => nil, +               "provider_name" => "example.com", +               "provider_url" => "https://example.com", +               "url" => "https://example.com/ogp-missing-data", +               "pleroma" => %{ +                 "opengraph" => %{ +                   "title" => "Pleroma", +                   "type" => "website", +                   "url" => "https://example.com/ogp-missing-data" +                 } +               } +             } +    end +  end + +  test "bookmarks" do +    %{conn: conn} = oauth_access(["write:bookmarks", "read:bookmarks"]) +    author = insert(:user) + +    {:ok, activity1} = +      CommonAPI.post(author, %{ +        "status" => "heweoo?" +      }) + +    {:ok, activity2} = +      CommonAPI.post(author, %{ +        "status" => "heweoo!" +      }) + +    response1 = post(conn, "/api/v1/statuses/#{activity1.id}/bookmark") + +    assert json_response(response1, 200)["bookmarked"] == true + +    response2 = post(conn, "/api/v1/statuses/#{activity2.id}/bookmark") + +    assert json_response(response2, 200)["bookmarked"] == true + +    bookmarks = get(conn, "/api/v1/bookmarks") + +    assert [json_response(response2, 200), json_response(response1, 200)] == +             json_response(bookmarks, 200) + +    response1 = post(conn, "/api/v1/statuses/#{activity1.id}/unbookmark") + +    assert json_response(response1, 200)["bookmarked"] == false + +    bookmarks = get(conn, "/api/v1/bookmarks") + +    assert [json_response(response2, 200)] == json_response(bookmarks, 200) +  end + +  describe "conversation muting" do +    setup do: oauth_access(["write:mutes"]) + +    setup do +      post_user = insert(:user) +      {:ok, activity} = CommonAPI.post(post_user, %{"status" => "HIE"}) +      %{activity: activity} +    end + +    test "mute conversation", %{conn: conn, activity: activity} do +      id_str = to_string(activity.id) + +      assert %{"id" => ^id_str, "muted" => true} = +               conn +               |> post("/api/v1/statuses/#{activity.id}/mute") +               |> json_response(200) +    end + +    test "cannot mute already muted conversation", %{conn: conn, user: user, activity: activity} do +      {:ok, _} = CommonAPI.add_mute(user, activity) + +      conn = post(conn, "/api/v1/statuses/#{activity.id}/mute") + +      assert json_response(conn, 400) == %{"error" => "conversation is already muted"} +    end + +    test "unmute conversation", %{conn: conn, user: user, activity: activity} do +      {:ok, _} = CommonAPI.add_mute(user, activity) + +      id_str = to_string(activity.id) + +      assert %{"id" => ^id_str, "muted" => false} = +               conn +               # |> assign(:user, user) +               |> post("/api/v1/statuses/#{activity.id}/unmute") +               |> json_response(200) +    end +  end + +  test "Repeated posts that are replies incorrectly have in_reply_to_id null", %{conn: conn} do +    user1 = insert(:user) +    user2 = insert(:user) +    user3 = insert(:user) + +    {:ok, replied_to} = CommonAPI.post(user1, %{"status" => "cofe"}) + +    # Reply to status from another user +    conn1 = +      conn +      |> assign(:user, user2) +      |> assign(:token, insert(:oauth_token, user: user2, scopes: ["write:statuses"])) +      |> post("/api/v1/statuses", %{"status" => "xD", "in_reply_to_id" => replied_to.id}) + +    assert %{"content" => "xD", "id" => id} = json_response(conn1, 200) + +    activity = Activity.get_by_id_with_object(id) + +    assert Object.normalize(activity).data["inReplyTo"] == Object.normalize(replied_to).data["id"] +    assert Activity.get_in_reply_to_activity(activity).id == replied_to.id + +    # Reblog from the third user +    conn2 = +      conn +      |> assign(:user, user3) +      |> assign(:token, insert(:oauth_token, user: user3, scopes: ["write:statuses"])) +      |> post("/api/v1/statuses/#{activity.id}/reblog") + +    assert %{"reblog" => %{"id" => id, "reblogged" => true, "reblogs_count" => 1}} = +             json_response(conn2, 200) + +    assert to_string(activity.id) == id + +    # Getting third user status +    conn3 = +      conn +      |> assign(:user, user3) +      |> assign(:token, insert(:oauth_token, user: user3, scopes: ["read:statuses"])) +      |> get("api/v1/timelines/home") + +    [reblogged_activity] = json_response(conn3, 200) + +    assert reblogged_activity["reblog"]["in_reply_to_id"] == replied_to.id + +    replied_to_user = User.get_by_ap_id(replied_to.data["actor"]) +    assert reblogged_activity["reblog"]["in_reply_to_account_id"] == replied_to_user.id +  end + +  describe "GET /api/v1/statuses/:id/favourited_by" do +    setup do: oauth_access(["read:accounts"]) + +    setup %{user: user} do +      {:ok, activity} = CommonAPI.post(user, %{"status" => "test"}) + +      %{activity: activity} +    end + +    test "returns users who have favorited the status", %{conn: conn, activity: activity} do +      other_user = insert(:user) +      {:ok, _, _} = CommonAPI.favorite(activity.id, other_user) + +      response = +        conn +        |> get("/api/v1/statuses/#{activity.id}/favourited_by") +        |> json_response(:ok) + +      [%{"id" => id}] = response + +      assert id == other_user.id +    end + +    test "returns empty array when status has not been favorited yet", %{ +      conn: conn, +      activity: activity +    } do +      response = +        conn +        |> get("/api/v1/statuses/#{activity.id}/favourited_by") +        |> json_response(:ok) + +      assert Enum.empty?(response) +    end + +    test "does not return users who have favorited the status but are blocked", %{ +      conn: %{assigns: %{user: user}} = conn, +      activity: activity +    } do +      other_user = insert(:user) +      {:ok, _user_relationship} = User.block(user, other_user) + +      {:ok, _, _} = CommonAPI.favorite(activity.id, other_user) + +      response = +        conn +        |> get("/api/v1/statuses/#{activity.id}/favourited_by") +        |> json_response(:ok) + +      assert Enum.empty?(response) +    end + +    test "does not fail on an unauthenticated request", %{activity: activity} do +      other_user = insert(:user) +      {:ok, _, _} = CommonAPI.favorite(activity.id, other_user) + +      response = +        build_conn() +        |> get("/api/v1/statuses/#{activity.id}/favourited_by") +        |> json_response(:ok) + +      [%{"id" => id}] = response +      assert id == other_user.id +    end + +    test "requires authentication for private posts", %{user: user} do +      other_user = insert(:user) + +      {:ok, activity} = +        CommonAPI.post(user, %{ +          "status" => "@#{other_user.nickname} wanna get some #cofe together?", +          "visibility" => "direct" +        }) + +      {:ok, _, _} = CommonAPI.favorite(activity.id, other_user) + +      favourited_by_url = "/api/v1/statuses/#{activity.id}/favourited_by" + +      build_conn() +      |> get(favourited_by_url) +      |> json_response(404) + +      conn = +        build_conn() +        |> assign(:user, other_user) +        |> assign(:token, insert(:oauth_token, user: other_user, scopes: ["read:accounts"])) + +      conn +      |> assign(:token, nil) +      |> get(favourited_by_url) +      |> json_response(404) + +      response = +        conn +        |> get(favourited_by_url) +        |> json_response(200) + +      [%{"id" => id}] = response +      assert id == other_user.id +    end +  end + +  describe "GET /api/v1/statuses/:id/reblogged_by" do +    setup do: oauth_access(["read:accounts"]) + +    setup %{user: user} do +      {:ok, activity} = CommonAPI.post(user, %{"status" => "test"}) + +      %{activity: activity} +    end + +    test "returns users who have reblogged the status", %{conn: conn, activity: activity} do +      other_user = insert(:user) +      {:ok, _, _} = CommonAPI.repeat(activity.id, other_user) + +      response = +        conn +        |> get("/api/v1/statuses/#{activity.id}/reblogged_by") +        |> json_response(:ok) + +      [%{"id" => id}] = response + +      assert id == other_user.id +    end + +    test "returns empty array when status has not been reblogged yet", %{ +      conn: conn, +      activity: activity +    } do +      response = +        conn +        |> get("/api/v1/statuses/#{activity.id}/reblogged_by") +        |> json_response(:ok) + +      assert Enum.empty?(response) +    end + +    test "does not return users who have reblogged the status but are blocked", %{ +      conn: %{assigns: %{user: user}} = conn, +      activity: activity +    } do +      other_user = insert(:user) +      {:ok, _user_relationship} = User.block(user, other_user) + +      {:ok, _, _} = CommonAPI.repeat(activity.id, other_user) + +      response = +        conn +        |> get("/api/v1/statuses/#{activity.id}/reblogged_by") +        |> json_response(:ok) + +      assert Enum.empty?(response) +    end + +    test "does not return users who have reblogged the status privately", %{ +      conn: conn, +      activity: activity +    } do +      other_user = insert(:user) + +      {:ok, _, _} = CommonAPI.repeat(activity.id, other_user, %{"visibility" => "private"}) + +      response = +        conn +        |> get("/api/v1/statuses/#{activity.id}/reblogged_by") +        |> json_response(:ok) + +      assert Enum.empty?(response) +    end + +    test "does not fail on an unauthenticated request", %{activity: activity} do +      other_user = insert(:user) +      {:ok, _, _} = CommonAPI.repeat(activity.id, other_user) + +      response = +        build_conn() +        |> get("/api/v1/statuses/#{activity.id}/reblogged_by") +        |> json_response(:ok) + +      [%{"id" => id}] = response +      assert id == other_user.id +    end + +    test "requires authentication for private posts", %{user: user} do +      other_user = insert(:user) + +      {:ok, activity} = +        CommonAPI.post(user, %{ +          "status" => "@#{other_user.nickname} wanna get some #cofe together?", +          "visibility" => "direct" +        }) + +      build_conn() +      |> get("/api/v1/statuses/#{activity.id}/reblogged_by") +      |> json_response(404) + +      response = +        build_conn() +        |> assign(:user, other_user) +        |> assign(:token, insert(:oauth_token, user: other_user, scopes: ["read:accounts"])) +        |> get("/api/v1/statuses/#{activity.id}/reblogged_by") +        |> json_response(200) + +      assert [] == response +    end +  end + +  test "context" do +    user = insert(:user) + +    {:ok, %{id: id1}} = CommonAPI.post(user, %{"status" => "1"}) +    {:ok, %{id: id2}} = CommonAPI.post(user, %{"status" => "2", "in_reply_to_status_id" => id1}) +    {:ok, %{id: id3}} = CommonAPI.post(user, %{"status" => "3", "in_reply_to_status_id" => id2}) +    {:ok, %{id: id4}} = CommonAPI.post(user, %{"status" => "4", "in_reply_to_status_id" => id3}) +    {:ok, %{id: id5}} = CommonAPI.post(user, %{"status" => "5", "in_reply_to_status_id" => id4}) + +    response = +      build_conn() +      |> get("/api/v1/statuses/#{id3}/context") +      |> json_response(:ok) + +    assert %{ +             "ancestors" => [%{"id" => ^id1}, %{"id" => ^id2}], +             "descendants" => [%{"id" => ^id4}, %{"id" => ^id5}] +           } = response +  end + +  test "returns the favorites of a user" do +    %{user: user, conn: conn} = oauth_access(["read:favourites"]) +    other_user = insert(:user) + +    {:ok, _} = CommonAPI.post(other_user, %{"status" => "bla"}) +    {:ok, activity} = CommonAPI.post(other_user, %{"status" => "traps are happy"}) + +    {:ok, _, _} = CommonAPI.favorite(activity.id, user) + +    first_conn = get(conn, "/api/v1/favourites") + +    assert [status] = json_response(first_conn, 200) +    assert status["id"] == to_string(activity.id) + +    assert [{"link", _link_header}] = +             Enum.filter(first_conn.resp_headers, fn element -> match?({"link", _}, element) end) + +    # Honours query params +    {:ok, second_activity} = +      CommonAPI.post(other_user, %{ +        "status" => +          "Trees Are Never Sad Look At Them Every Once In Awhile They're Quite Beautiful." +      }) + +    {:ok, _, _} = CommonAPI.favorite(second_activity.id, user) + +    last_like = status["id"] + +    second_conn = get(conn, "/api/v1/favourites?since_id=#{last_like}") + +    assert [second_status] = json_response(second_conn, 200) +    assert second_status["id"] == to_string(second_activity.id) + +    third_conn = get(conn, "/api/v1/favourites?limit=0") + +    assert [] = json_response(third_conn, 200) +  end +end diff --git a/test/web/mastodon_api/subscription_controller_test.exs b/test/web/mastodon_api/controllers/subscription_controller_test.exs index 7dfb02f63..7dfb02f63 100644 --- a/test/web/mastodon_api/subscription_controller_test.exs +++ b/test/web/mastodon_api/controllers/subscription_controller_test.exs diff --git a/test/web/mastodon_api/controllers/suggestion_controller_test.exs b/test/web/mastodon_api/controllers/suggestion_controller_test.exs new file mode 100644 index 000000000..0319d3475 --- /dev/null +++ b/test/web/mastodon_api/controllers/suggestion_controller_test.exs @@ -0,0 +1,46 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.MastodonAPI.SuggestionControllerTest do +  use Pleroma.Web.ConnCase + +  alias Pleroma.Config + +  import Pleroma.Factory +  import Tesla.Mock + +  setup do: oauth_access(["read"]) + +  setup %{user: user} do +    other_user = insert(:user) +    host = Config.get([Pleroma.Web.Endpoint, :url, :host]) +    url500 = "http://test500?#{host}&#{user.nickname}" +    url200 = "http://test200?#{host}&#{user.nickname}" + +    mock(fn +      %{method: :get, url: ^url500} -> +        %Tesla.Env{status: 500, body: "bad request"} + +      %{method: :get, url: ^url200} -> +        %Tesla.Env{ +          status: 200, +          body: +            ~s([{"acct":"yj455","avatar":"https://social.heldscal.la/avatar/201.jpeg","avatar_static":"https://social.heldscal.la/avatar/s/201.jpeg"}, {"acct":"#{ +              other_user.ap_id +            }","avatar":"https://social.heldscal.la/avatar/202.jpeg","avatar_static":"https://social.heldscal.la/avatar/s/202.jpeg"}]) +        } +    end) + +    [other_user: other_user] +  end + +  test "returns empty result", %{conn: conn} do +    res = +      conn +      |> get("/api/v1/suggestions") +      |> json_response(200) + +    assert res == [] +  end +end diff --git a/test/web/mastodon_api/controllers/timeline_controller_test.exs b/test/web/mastodon_api/controllers/timeline_controller_test.exs new file mode 100644 index 000000000..bb94d8e5a --- /dev/null +++ b/test/web/mastodon_api/controllers/timeline_controller_test.exs @@ -0,0 +1,289 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.MastodonAPI.TimelineControllerTest do +  use Pleroma.Web.ConnCase + +  import Pleroma.Factory +  import Tesla.Mock + +  alias Pleroma.Config +  alias Pleroma.User +  alias Pleroma.Web.CommonAPI + +  clear_config([:instance, :public]) + +  setup do +    mock(fn env -> apply(HttpRequestMock, :request, [env]) end) +    :ok +  end + +  describe "home" do +    setup do: oauth_access(["read:statuses"]) + +    test "the home timeline", %{user: user, conn: conn} do +      following = insert(:user) + +      {:ok, _activity} = CommonAPI.post(following, %{"status" => "test"}) + +      ret_conn = get(conn, "/api/v1/timelines/home") + +      assert Enum.empty?(json_response(ret_conn, :ok)) + +      {:ok, _user} = User.follow(user, following) + +      conn = get(conn, "/api/v1/timelines/home") + +      assert [%{"content" => "test"}] = json_response(conn, :ok) +    end + +    test "the home timeline when the direct messages are excluded", %{user: user, conn: conn} do +      {:ok, public_activity} = CommonAPI.post(user, %{"status" => ".", "visibility" => "public"}) +      {:ok, direct_activity} = CommonAPI.post(user, %{"status" => ".", "visibility" => "direct"}) + +      {:ok, unlisted_activity} = +        CommonAPI.post(user, %{"status" => ".", "visibility" => "unlisted"}) + +      {:ok, private_activity} = +        CommonAPI.post(user, %{"status" => ".", "visibility" => "private"}) + +      conn = get(conn, "/api/v1/timelines/home", %{"exclude_visibilities" => ["direct"]}) + +      assert status_ids = json_response(conn, :ok) |> Enum.map(& &1["id"]) +      assert public_activity.id in status_ids +      assert unlisted_activity.id in status_ids +      assert private_activity.id in status_ids +      refute direct_activity.id in status_ids +    end +  end + +  describe "public" do +    @tag capture_log: true +    test "the public timeline", %{conn: conn} do +      following = insert(:user) + +      {:ok, _activity} = CommonAPI.post(following, %{"status" => "test"}) + +      _activity = insert(:note_activity, local: false) + +      conn = get(conn, "/api/v1/timelines/public", %{"local" => "False"}) + +      assert length(json_response(conn, :ok)) == 2 + +      conn = get(build_conn(), "/api/v1/timelines/public", %{"local" => "True"}) + +      assert [%{"content" => "test"}] = json_response(conn, :ok) + +      conn = get(build_conn(), "/api/v1/timelines/public", %{"local" => "1"}) + +      assert [%{"content" => "test"}] = json_response(conn, :ok) +    end + +    test "the public timeline when public is set to false", %{conn: conn} do +      Config.put([:instance, :public], false) + +      assert %{"error" => "This resource requires authentication."} == +               conn +               |> get("/api/v1/timelines/public", %{"local" => "False"}) +               |> json_response(:forbidden) +    end + +    test "the public timeline includes only public statuses for an authenticated user" do +      %{user: user, conn: conn} = oauth_access(["read:statuses"]) + +      {:ok, _activity} = CommonAPI.post(user, %{"status" => "test"}) +      {:ok, _activity} = CommonAPI.post(user, %{"status" => "test", "visibility" => "private"}) +      {:ok, _activity} = CommonAPI.post(user, %{"status" => "test", "visibility" => "unlisted"}) +      {:ok, _activity} = CommonAPI.post(user, %{"status" => "test", "visibility" => "direct"}) + +      res_conn = get(conn, "/api/v1/timelines/public") +      assert length(json_response(res_conn, 200)) == 1 +    end +  end + +  describe "direct" do +    test "direct timeline", %{conn: conn} do +      user_one = insert(:user) +      user_two = insert(:user) + +      {:ok, user_two} = User.follow(user_two, user_one) + +      {:ok, direct} = +        CommonAPI.post(user_one, %{ +          "status" => "Hi @#{user_two.nickname}!", +          "visibility" => "direct" +        }) + +      {:ok, _follower_only} = +        CommonAPI.post(user_one, %{ +          "status" => "Hi @#{user_two.nickname}!", +          "visibility" => "private" +        }) + +      conn_user_two = +        conn +        |> assign(:user, user_two) +        |> assign(:token, insert(:oauth_token, user: user_two, scopes: ["read:statuses"])) + +      # Only direct should be visible here +      res_conn = get(conn_user_two, "api/v1/timelines/direct") + +      [status] = json_response(res_conn, :ok) + +      assert %{"visibility" => "direct"} = status +      assert status["url"] != direct.data["id"] + +      # User should be able to see their own direct message +      res_conn = +        build_conn() +        |> assign(:user, user_one) +        |> assign(:token, insert(:oauth_token, user: user_one, scopes: ["read:statuses"])) +        |> get("api/v1/timelines/direct") + +      [status] = json_response(res_conn, :ok) + +      assert %{"visibility" => "direct"} = status + +      # Both should be visible here +      res_conn = get(conn_user_two, "api/v1/timelines/home") + +      [_s1, _s2] = json_response(res_conn, :ok) + +      # Test pagination +      Enum.each(1..20, fn _ -> +        {:ok, _} = +          CommonAPI.post(user_one, %{ +            "status" => "Hi @#{user_two.nickname}!", +            "visibility" => "direct" +          }) +      end) + +      res_conn = get(conn_user_two, "api/v1/timelines/direct") + +      statuses = json_response(res_conn, :ok) +      assert length(statuses) == 20 + +      res_conn = +        get(conn_user_two, "api/v1/timelines/direct", %{max_id: List.last(statuses)["id"]}) + +      [status] = json_response(res_conn, :ok) + +      assert status["url"] != direct.data["id"] +    end + +    test "doesn't include DMs from blocked users" do +      %{user: blocker, conn: conn} = oauth_access(["read:statuses"]) +      blocked = insert(:user) +      other_user = insert(:user) +      {:ok, _user_relationship} = User.block(blocker, blocked) + +      {:ok, _blocked_direct} = +        CommonAPI.post(blocked, %{ +          "status" => "Hi @#{blocker.nickname}!", +          "visibility" => "direct" +        }) + +      {:ok, direct} = +        CommonAPI.post(other_user, %{ +          "status" => "Hi @#{blocker.nickname}!", +          "visibility" => "direct" +        }) + +      res_conn = get(conn, "api/v1/timelines/direct") + +      [status] = json_response(res_conn, :ok) +      assert status["id"] == direct.id +    end +  end + +  describe "list" do +    setup do: oauth_access(["read:lists"]) + +    test "list timeline", %{user: user, conn: conn} do +      other_user = insert(:user) +      {:ok, _activity_one} = CommonAPI.post(user, %{"status" => "Marisa is cute."}) +      {:ok, activity_two} = CommonAPI.post(other_user, %{"status" => "Marisa is cute."}) +      {:ok, list} = Pleroma.List.create("name", user) +      {:ok, list} = Pleroma.List.follow(list, other_user) + +      conn = get(conn, "/api/v1/timelines/list/#{list.id}") + +      assert [%{"id" => id}] = json_response(conn, :ok) + +      assert id == to_string(activity_two.id) +    end + +    test "list timeline does not leak non-public statuses for unfollowed users", %{ +      user: user, +      conn: conn +    } do +      other_user = insert(:user) +      {:ok, activity_one} = CommonAPI.post(other_user, %{"status" => "Marisa is cute."}) + +      {:ok, _activity_two} = +        CommonAPI.post(other_user, %{ +          "status" => "Marisa is cute.", +          "visibility" => "private" +        }) + +      {:ok, list} = Pleroma.List.create("name", user) +      {:ok, list} = Pleroma.List.follow(list, other_user) + +      conn = get(conn, "/api/v1/timelines/list/#{list.id}") + +      assert [%{"id" => id}] = json_response(conn, :ok) + +      assert id == to_string(activity_one.id) +    end +  end + +  describe "hashtag" do +    setup do: oauth_access(["n/a"]) + +    @tag capture_log: true +    test "hashtag timeline", %{conn: conn} do +      following = insert(:user) + +      {:ok, activity} = CommonAPI.post(following, %{"status" => "test #2hu"}) + +      nconn = get(conn, "/api/v1/timelines/tag/2hu") + +      assert [%{"id" => id}] = json_response(nconn, :ok) + +      assert id == to_string(activity.id) + +      # works for different capitalization too +      nconn = get(conn, "/api/v1/timelines/tag/2HU") + +      assert [%{"id" => id}] = json_response(nconn, :ok) + +      assert id == to_string(activity.id) +    end + +    test "multi-hashtag timeline", %{conn: conn} do +      user = insert(:user) + +      {:ok, activity_test} = CommonAPI.post(user, %{"status" => "#test"}) +      {:ok, activity_test1} = CommonAPI.post(user, %{"status" => "#test #test1"}) +      {:ok, activity_none} = CommonAPI.post(user, %{"status" => "#test #none"}) + +      any_test = get(conn, "/api/v1/timelines/tag/test", %{"any" => ["test1"]}) + +      [status_none, status_test1, status_test] = json_response(any_test, :ok) + +      assert to_string(activity_test.id) == status_test["id"] +      assert to_string(activity_test1.id) == status_test1["id"] +      assert to_string(activity_none.id) == status_none["id"] + +      restricted_test = +        get(conn, "/api/v1/timelines/tag/test", %{"all" => ["test1"], "none" => ["none"]}) + +      assert [status_test1] == json_response(restricted_test, :ok) + +      all_test = get(conn, "/api/v1/timelines/tag/test", %{"all" => ["none"]}) + +      assert [status_none] == json_response(all_test, :ok) +    end +  end +end diff --git a/test/web/mastodon_api/list_view_test.exs b/test/web/mastodon_api/list_view_test.exs deleted file mode 100644 index 73143467f..000000000 --- a/test/web/mastodon_api/list_view_test.exs +++ /dev/null @@ -1,22 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/> -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Web.MastodonAPI.ListViewTest do -  use Pleroma.DataCase -  import Pleroma.Factory -  alias Pleroma.Web.MastodonAPI.ListView - -  test "Represent a list" do -    user = insert(:user) -    title = "mortal enemies" -    {:ok, list} = Pleroma.List.create(title, user) - -    expected = %{ -      id: to_string(list.id), -      title: title -    } - -    assert expected == ListView.render("list.json", %{list: list}) -  end -end diff --git a/test/web/mastodon_api/mastodon_api_controller/update_credentials_test.exs b/test/web/mastodon_api/mastodon_api_controller/update_credentials_test.exs deleted file mode 100644 index 71d0c8af8..000000000 --- a/test/web/mastodon_api/mastodon_api_controller/update_credentials_test.exs +++ /dev/null @@ -1,304 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Web.MastodonAPI.MastodonAPIController.UpdateCredentialsTest do -  alias Pleroma.Repo -  alias Pleroma.User - -  use Pleroma.Web.ConnCase - -  import Pleroma.Factory - -  describe "updating credentials" do -    test "sets user settings in a generic way", %{conn: conn} do -      user = insert(:user) - -      res_conn = -        conn -        |> assign(:user, user) -        |> patch("/api/v1/accounts/update_credentials", %{ -          "pleroma_settings_store" => %{ -            pleroma_fe: %{ -              theme: "bla" -            } -          } -        }) - -      assert user = json_response(res_conn, 200) -      assert user["pleroma"]["settings_store"] == %{"pleroma_fe" => %{"theme" => "bla"}} - -      user = Repo.get(User, user["id"]) - -      res_conn = -        conn -        |> assign(:user, user) -        |> patch("/api/v1/accounts/update_credentials", %{ -          "pleroma_settings_store" => %{ -            masto_fe: %{ -              theme: "bla" -            } -          } -        }) - -      assert user = json_response(res_conn, 200) - -      assert user["pleroma"]["settings_store"] == -               %{ -                 "pleroma_fe" => %{"theme" => "bla"}, -                 "masto_fe" => %{"theme" => "bla"} -               } - -      user = Repo.get(User, user["id"]) - -      res_conn = -        conn -        |> assign(:user, user) -        |> patch("/api/v1/accounts/update_credentials", %{ -          "pleroma_settings_store" => %{ -            masto_fe: %{ -              theme: "blub" -            } -          } -        }) - -      assert user = json_response(res_conn, 200) - -      assert user["pleroma"]["settings_store"] == -               %{ -                 "pleroma_fe" => %{"theme" => "bla"}, -                 "masto_fe" => %{"theme" => "blub"} -               } -    end - -    test "updates the user's bio", %{conn: conn} do -      user = insert(:user) -      user2 = insert(:user) - -      conn = -        conn -        |> assign(:user, user) -        |> patch("/api/v1/accounts/update_credentials", %{ -          "note" => "I drink #cofe with @#{user2.nickname}" -        }) - -      assert user = json_response(conn, 200) - -      assert user["note"] == -               ~s(I drink <a class="hashtag" data-tag="cofe" href="http://localhost:4001/tag/cofe" rel="tag">#cofe</a> with <span class="h-card"><a data-user=") <> -                 user2.id <> -                 ~s(" class="u-url mention" href=") <> -                 user2.ap_id <> ~s(">@<span>) <> user2.nickname <> ~s(</span></a></span>) -    end - -    test "updates the user's locking status", %{conn: conn} do -      user = insert(:user) - -      conn = -        conn -        |> assign(:user, user) -        |> patch("/api/v1/accounts/update_credentials", %{locked: "true"}) - -      assert user = json_response(conn, 200) -      assert user["locked"] == true -    end - -    test "updates the user's default scope", %{conn: conn} do -      user = insert(:user) - -      conn = -        conn -        |> assign(:user, user) -        |> patch("/api/v1/accounts/update_credentials", %{default_scope: "cofe"}) - -      assert user = json_response(conn, 200) -      assert user["source"]["privacy"] == "cofe" -    end - -    test "updates the user's hide_followers status", %{conn: conn} do -      user = insert(:user) - -      conn = -        conn -        |> assign(:user, user) -        |> patch("/api/v1/accounts/update_credentials", %{hide_followers: "true"}) - -      assert user = json_response(conn, 200) -      assert user["pleroma"]["hide_followers"] == true -    end - -    test "updates the user's skip_thread_containment option", %{conn: conn} do -      user = insert(:user) - -      response = -        conn -        |> assign(:user, user) -        |> patch("/api/v1/accounts/update_credentials", %{skip_thread_containment: "true"}) -        |> json_response(200) - -      assert response["pleroma"]["skip_thread_containment"] == true -      assert refresh_record(user).info.skip_thread_containment -    end - -    test "updates the user's hide_follows status", %{conn: conn} do -      user = insert(:user) - -      conn = -        conn -        |> assign(:user, user) -        |> patch("/api/v1/accounts/update_credentials", %{hide_follows: "true"}) - -      assert user = json_response(conn, 200) -      assert user["pleroma"]["hide_follows"] == true -    end - -    test "updates the user's hide_favorites status", %{conn: conn} do -      user = insert(:user) - -      conn = -        conn -        |> assign(:user, user) -        |> patch("/api/v1/accounts/update_credentials", %{hide_favorites: "true"}) - -      assert user = json_response(conn, 200) -      assert user["pleroma"]["hide_favorites"] == true -    end - -    test "updates the user's show_role status", %{conn: conn} do -      user = insert(:user) - -      conn = -        conn -        |> assign(:user, user) -        |> patch("/api/v1/accounts/update_credentials", %{show_role: "false"}) - -      assert user = json_response(conn, 200) -      assert user["source"]["pleroma"]["show_role"] == false -    end - -    test "updates the user's no_rich_text status", %{conn: conn} do -      user = insert(:user) - -      conn = -        conn -        |> assign(:user, user) -        |> patch("/api/v1/accounts/update_credentials", %{no_rich_text: "true"}) - -      assert user = json_response(conn, 200) -      assert user["source"]["pleroma"]["no_rich_text"] == true -    end - -    test "updates the user's name", %{conn: conn} do -      user = insert(:user) - -      conn = -        conn -        |> assign(:user, user) -        |> patch("/api/v1/accounts/update_credentials", %{"display_name" => "markorepairs"}) - -      assert user = json_response(conn, 200) -      assert user["display_name"] == "markorepairs" -    end - -    test "updates the user's avatar", %{conn: conn} do -      user = insert(:user) - -      new_avatar = %Plug.Upload{ -        content_type: "image/jpg", -        path: Path.absname("test/fixtures/image.jpg"), -        filename: "an_image.jpg" -      } - -      conn = -        conn -        |> assign(:user, user) -        |> patch("/api/v1/accounts/update_credentials", %{"avatar" => new_avatar}) - -      assert user_response = json_response(conn, 200) -      assert user_response["avatar"] != User.avatar_url(user) -    end - -    test "updates the user's banner", %{conn: conn} do -      user = insert(:user) - -      new_header = %Plug.Upload{ -        content_type: "image/jpg", -        path: Path.absname("test/fixtures/image.jpg"), -        filename: "an_image.jpg" -      } - -      conn = -        conn -        |> assign(:user, user) -        |> patch("/api/v1/accounts/update_credentials", %{"header" => new_header}) - -      assert user_response = json_response(conn, 200) -      assert user_response["header"] != User.banner_url(user) -    end - -    test "updates the user's background", %{conn: conn} do -      user = insert(:user) - -      new_header = %Plug.Upload{ -        content_type: "image/jpg", -        path: Path.absname("test/fixtures/image.jpg"), -        filename: "an_image.jpg" -      } - -      conn = -        conn -        |> assign(:user, user) -        |> patch("/api/v1/accounts/update_credentials", %{ -          "pleroma_background_image" => new_header -        }) - -      assert user_response = json_response(conn, 200) -      assert user_response["pleroma"]["background_image"] -    end - -    test "requires 'write' permission", %{conn: conn} do -      token1 = insert(:oauth_token, scopes: ["read"]) -      token2 = insert(:oauth_token, scopes: ["write", "follow"]) - -      for token <- [token1, token2] do -        conn = -          conn -          |> put_req_header("authorization", "Bearer #{token.token}") -          |> patch("/api/v1/accounts/update_credentials", %{}) - -        if token == token1 do -          assert %{"error" => "Insufficient permissions: write."} == json_response(conn, 403) -        else -          assert json_response(conn, 200) -        end -      end -    end - -    test "updates profile emojos", %{conn: conn} do -      user = insert(:user) - -      note = "*sips :blank:*" -      name = "I am :firefox:" - -      conn = -        conn -        |> assign(:user, user) -        |> patch("/api/v1/accounts/update_credentials", %{ -          "note" => note, -          "display_name" => name -        }) - -      assert json_response(conn, 200) - -      conn = -        conn -        |> get("/api/v1/accounts/#{user.id}") - -      assert user = json_response(conn, 200) - -      assert user["note"] == note -      assert user["display_name"] == name -      assert [%{"shortcode" => "blank"}, %{"shortcode" => "firefox"}] = user["emojis"] -    end -  end -end diff --git a/test/web/mastodon_api/mastodon_api_controller_test.exs b/test/web/mastodon_api/mastodon_api_controller_test.exs index b4b1dd785..c1f70f9fe 100644 --- a/test/web/mastodon_api/mastodon_api_controller_test.exs +++ b/test/web/mastodon_api/mastodon_api_controller_test.exs @@ -5,3858 +5,37 @@  defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do    use Pleroma.Web.ConnCase -  alias Ecto.Changeset -  alias Pleroma.Activity -  alias Pleroma.Notification -  alias Pleroma.Object -  alias Pleroma.Repo -  alias Pleroma.ScheduledActivity -  alias Pleroma.User -  alias Pleroma.Web.ActivityPub.ActivityPub -  alias Pleroma.Web.CommonAPI -  alias Pleroma.Web.MastodonAPI.FilterView -  alias Pleroma.Web.OAuth.App -  alias Pleroma.Web.OAuth.Token -  alias Pleroma.Web.OStatus -  alias Pleroma.Web.Push -  alias Pleroma.Web.TwitterAPI.TwitterAPI -  import Pleroma.Factory -  import ExUnit.CaptureLog -  import Tesla.Mock -  import Swoosh.TestAssertions +  describe "empty_array/2 (stubs)" do +    test "GET /api/v1/accounts/:id/identity_proofs" do +      %{user: user, conn: conn} = oauth_access(["n/a"]) -  @image "data:image/gif;base64,R0lGODlhEAAQAMQAAORHHOVSKudfOulrSOp3WOyDZu6QdvCchPGolfO0o/XBs/fNwfjZ0frl3/zy7////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAkAABAALAAAAAAQABAAAAVVICSOZGlCQAosJ6mu7fiyZeKqNKToQGDsM8hBADgUXoGAiqhSvp5QAnQKGIgUhwFUYLCVDFCrKUE1lBavAViFIDlTImbKC5Gm2hB0SlBCBMQiB0UjIQA7" - -  setup do -    mock(fn env -> apply(HttpRequestMock, :request, [env]) end) -    :ok -  end - -  test "the home timeline", %{conn: conn} do -    user = insert(:user) -    following = insert(:user) - -    {:ok, _activity} = CommonAPI.post(following, %{"status" => "test"}) - -    conn = -      conn -      |> assign(:user, user) -      |> get("/api/v1/timelines/home") - -    assert Enum.empty?(json_response(conn, 200)) - -    {:ok, user} = User.follow(user, following) - -    conn = -      build_conn() -      |> assign(:user, user) -      |> get("/api/v1/timelines/home") - -    assert [%{"content" => "test"}] = json_response(conn, 200) -  end - -  test "the public timeline", %{conn: conn} do -    following = insert(:user) - -    capture_log(fn -> -      {:ok, _activity} = CommonAPI.post(following, %{"status" => "test"}) - -      {:ok, [_activity]} = -        OStatus.fetch_activity_from_url("https://shitposter.club/notice/2827873") - -      conn = -        conn -        |> get("/api/v1/timelines/public", %{"local" => "False"}) - -      assert length(json_response(conn, 200)) == 2 - -      conn = -        build_conn() -        |> get("/api/v1/timelines/public", %{"local" => "True"}) - -      assert [%{"content" => "test"}] = json_response(conn, 200) - -      conn = -        build_conn() -        |> get("/api/v1/timelines/public", %{"local" => "1"}) - -      assert [%{"content" => "test"}] = json_response(conn, 200) -    end) -  end - -  test "the public timeline when public is set to false", %{conn: conn} do -    public = Pleroma.Config.get([:instance, :public]) -    Pleroma.Config.put([:instance, :public], false) - -    on_exit(fn -> -      Pleroma.Config.put([:instance, :public], public) -    end) - -    assert conn -           |> get("/api/v1/timelines/public", %{"local" => "False"}) -           |> json_response(403) == %{"error" => "This resource requires authentication."} -  end - -  describe "posting statuses" do -    setup do -      user = insert(:user) - -      conn = -        build_conn() -        |> assign(:user, user) - -      [conn: conn] -    end - -    test "posting a status", %{conn: conn} do -      idempotency_key = "Pikachu rocks!" - -      conn_one = -        conn -        |> put_req_header("idempotency-key", idempotency_key) -        |> post("/api/v1/statuses", %{ -          "status" => "cofe", -          "spoiler_text" => "2hu", -          "sensitive" => "false" -        }) - -      {:ok, ttl} = Cachex.ttl(:idempotency_cache, idempotency_key) -      # Six hours -      assert ttl > :timer.seconds(6 * 60 * 60 - 1) - -      assert %{"content" => "cofe", "id" => id, "spoiler_text" => "2hu", "sensitive" => false} = -               json_response(conn_one, 200) - -      assert Activity.get_by_id(id) - -      conn_two = -        conn -        |> put_req_header("idempotency-key", idempotency_key) -        |> post("/api/v1/statuses", %{ -          "status" => "cofe", -          "spoiler_text" => "2hu", -          "sensitive" => "false" -        }) - -      assert %{"id" => second_id} = json_response(conn_two, 200) -      assert id == second_id - -      conn_three = -        conn -        |> post("/api/v1/statuses", %{ -          "status" => "cofe", -          "spoiler_text" => "2hu", -          "sensitive" => "false" -        }) - -      assert %{"id" => third_id} = json_response(conn_three, 200) -      refute id == third_id -    end - -    test "replying to a status", %{conn: conn} do -      user = insert(:user) -      {:ok, replied_to} = CommonAPI.post(user, %{"status" => "cofe"}) - -      conn = -        conn -        |> post("/api/v1/statuses", %{"status" => "xD", "in_reply_to_id" => replied_to.id}) - -      assert %{"content" => "xD", "id" => id} = json_response(conn, 200) - -      activity = Activity.get_by_id(id) - -      assert activity.data["context"] == replied_to.data["context"] -      assert Activity.get_in_reply_to_activity(activity).id == replied_to.id -    end - -    test "replying to a direct message with visibility other than direct", %{conn: conn} do -      user = insert(:user) -      {:ok, replied_to} = CommonAPI.post(user, %{"status" => "suya..", "visibility" => "direct"}) - -      Enum.each(["public", "private", "unlisted"], fn visibility -> -        conn = -          conn -          |> post("/api/v1/statuses", %{ -            "status" => "@#{user.nickname} hey", -            "in_reply_to_id" => replied_to.id, -            "visibility" => visibility -          }) - -        assert json_response(conn, 422) == %{"error" => "The message visibility must be direct"} -      end) -    end - -    test "posting a status with an invalid in_reply_to_id", %{conn: conn} do -      conn = -        conn -        |> post("/api/v1/statuses", %{"status" => "xD", "in_reply_to_id" => ""}) - -      assert %{"content" => "xD", "id" => id} = json_response(conn, 200) -      assert Activity.get_by_id(id) -    end - -    test "posting a sensitive status", %{conn: conn} do -      conn = -        conn -        |> post("/api/v1/statuses", %{"status" => "cofe", "sensitive" => true}) - -      assert %{"content" => "cofe", "id" => id, "sensitive" => true} = json_response(conn, 200) -      assert Activity.get_by_id(id) -    end - -    test "posting a fake status", %{conn: conn} do -      real_conn = -        conn -        |> post("/api/v1/statuses", %{ -          "status" => -            "\"Tenshi Eating a Corndog\" is a much discussed concept on /jp/. The significance of it is disputed, so I will focus on one core concept: the symbolism behind it" -        }) - -      real_status = json_response(real_conn, 200) - -      assert real_status -      assert Object.get_by_ap_id(real_status["uri"]) - -      real_status = -        real_status -        |> Map.put("id", nil) -        |> Map.put("url", nil) -        |> Map.put("uri", nil) -        |> Map.put("created_at", nil) -        |> Kernel.put_in(["pleroma", "conversation_id"], nil) - -      fake_conn = -        conn -        |> post("/api/v1/statuses", %{ -          "status" => -            "\"Tenshi Eating a Corndog\" is a much discussed concept on /jp/. The significance of it is disputed, so I will focus on one core concept: the symbolism behind it", -          "preview" => true -        }) - -      fake_status = json_response(fake_conn, 200) - -      assert fake_status -      refute Object.get_by_ap_id(fake_status["uri"]) - -      fake_status = -        fake_status -        |> Map.put("id", nil) -        |> Map.put("url", nil) -        |> Map.put("uri", nil) -        |> Map.put("created_at", nil) -        |> Kernel.put_in(["pleroma", "conversation_id"], nil) - -      assert real_status == fake_status -    end - -    test "posting a status with OGP link preview", %{conn: conn} do -      Pleroma.Config.put([:rich_media, :enabled], true) - -      conn = -        conn -        |> post("/api/v1/statuses", %{ -          "status" => "https://example.com/ogp" -        }) - -      assert %{"id" => id, "card" => %{"title" => "The Rock"}} = json_response(conn, 200) -      assert Activity.get_by_id(id) -      Pleroma.Config.put([:rich_media, :enabled], false) -    end - -    test "posting a direct status", %{conn: conn} do -      user2 = insert(:user) -      content = "direct cofe @#{user2.nickname}" - -      conn = -        conn -        |> post("api/v1/statuses", %{"status" => content, "visibility" => "direct"}) - -      assert %{"id" => id, "visibility" => "direct"} = json_response(conn, 200) -      assert activity = Activity.get_by_id(id) -      assert activity.recipients == [user2.ap_id, conn.assigns[:user].ap_id] -      assert activity.data["to"] == [user2.ap_id] -      assert activity.data["cc"] == [] -    end -  end - -  describe "posting polls" do -    test "posting a poll", %{conn: conn} do -      user = insert(:user) -      time = NaiveDateTime.utc_now() - -      conn = +      res =          conn          |> assign(:user, user) -        |> post("/api/v1/statuses", %{ -          "status" => "Who is the #bestgrill?", -          "poll" => %{"options" => ["Rei", "Asuka", "Misato"], "expires_in" => 420} -        }) - -      response = json_response(conn, 200) - -      assert Enum.all?(response["poll"]["options"], fn %{"title" => title} -> -               title in ["Rei", "Asuka", "Misato"] -             end) - -      assert NaiveDateTime.diff(NaiveDateTime.from_iso8601!(response["poll"]["expires_at"]), time) in 420..430 -      refute response["poll"]["expred"] -    end - -    test "option limit is enforced", %{conn: conn} do -      user = insert(:user) -      limit = Pleroma.Config.get([:instance, :poll_limits, :max_options]) - -      conn = -        conn -        |> assign(:user, user) -        |> post("/api/v1/statuses", %{ -          "status" => "desu~", -          "poll" => %{"options" => Enum.map(0..limit, fn _ -> "desu" end), "expires_in" => 1} -        }) - -      %{"error" => error} = json_response(conn, 422) -      assert error == "Poll can't contain more than #{limit} options" -    end - -    test "option character limit is enforced", %{conn: conn} do -      user = insert(:user) -      limit = Pleroma.Config.get([:instance, :poll_limits, :max_option_chars]) - -      conn = -        conn -        |> assign(:user, user) -        |> post("/api/v1/statuses", %{ -          "status" => "...", -          "poll" => %{ -            "options" => [Enum.reduce(0..limit, "", fn _, acc -> acc <> "." end)], -            "expires_in" => 1 -          } -        }) - -      %{"error" => error} = json_response(conn, 422) -      assert error == "Poll options cannot be longer than #{limit} characters each" -    end - -    test "minimal date limit is enforced", %{conn: conn} do -      user = insert(:user) -      limit = Pleroma.Config.get([:instance, :poll_limits, :min_expiration]) - -      conn = -        conn -        |> assign(:user, user) -        |> post("/api/v1/statuses", %{ -          "status" => "imagine arbitrary limits", -          "poll" => %{ -            "options" => ["this post was made by pleroma gang"], -            "expires_in" => limit - 1 -          } -        }) - -      %{"error" => error} = json_response(conn, 422) -      assert error == "Expiration date is too soon" -    end - -    test "maximum date limit is enforced", %{conn: conn} do -      user = insert(:user) -      limit = Pleroma.Config.get([:instance, :poll_limits, :max_expiration]) - -      conn = -        conn -        |> assign(:user, user) -        |> post("/api/v1/statuses", %{ -          "status" => "imagine arbitrary limits", -          "poll" => %{ -            "options" => ["this post was made by pleroma gang"], -            "expires_in" => limit + 1 -          } -        }) - -      %{"error" => error} = json_response(conn, 422) -      assert error == "Expiration date is too far in the future" -    end -  end - -  test "direct timeline", %{conn: conn} do -    user_one = insert(:user) -    user_two = insert(:user) - -    {:ok, user_two} = User.follow(user_two, user_one) - -    {:ok, direct} = -      CommonAPI.post(user_one, %{ -        "status" => "Hi @#{user_two.nickname}!", -        "visibility" => "direct" -      }) - -    {:ok, _follower_only} = -      CommonAPI.post(user_one, %{ -        "status" => "Hi @#{user_two.nickname}!", -        "visibility" => "private" -      }) - -    # Only direct should be visible here -    res_conn = -      conn -      |> assign(:user, user_two) -      |> get("api/v1/timelines/direct") - -    [status] = json_response(res_conn, 200) - -    assert %{"visibility" => "direct"} = status -    assert status["url"] != direct.data["id"] - -    # User should be able to see his own direct message -    res_conn = -      build_conn() -      |> assign(:user, user_one) -      |> get("api/v1/timelines/direct") - -    [status] = json_response(res_conn, 200) - -    assert %{"visibility" => "direct"} = status - -    # Both should be visible here -    res_conn = -      conn -      |> assign(:user, user_two) -      |> get("api/v1/timelines/home") - -    [_s1, _s2] = json_response(res_conn, 200) - -    # Test pagination -    Enum.each(1..20, fn _ -> -      {:ok, _} = -        CommonAPI.post(user_one, %{ -          "status" => "Hi @#{user_two.nickname}!", -          "visibility" => "direct" -        }) -    end) - -    res_conn = -      conn -      |> assign(:user, user_two) -      |> get("api/v1/timelines/direct") - -    statuses = json_response(res_conn, 200) -    assert length(statuses) == 20 - -    res_conn = -      conn -      |> assign(:user, user_two) -      |> get("api/v1/timelines/direct", %{max_id: List.last(statuses)["id"]}) - -    [status] = json_response(res_conn, 200) - -    assert status["url"] != direct.data["id"] -  end - -  test "Conversations", %{conn: conn} do -    user_one = insert(:user) -    user_two = insert(:user) -    user_three = insert(:user) - -    {:ok, user_two} = User.follow(user_two, user_one) - -    {:ok, direct} = -      CommonAPI.post(user_one, %{ -        "status" => "Hi @#{user_two.nickname}, @#{user_three.nickname}!", -        "visibility" => "direct" -      }) - -    {:ok, _follower_only} = -      CommonAPI.post(user_one, %{ -        "status" => "Hi @#{user_two.nickname}!", -        "visibility" => "private" -      }) - -    res_conn = -      conn -      |> assign(:user, user_one) -      |> get("/api/v1/conversations") - -    assert response = json_response(res_conn, 200) - -    assert [ -             %{ -               "id" => res_id, -               "accounts" => res_accounts, -               "last_status" => res_last_status, -               "unread" => unread -             } -           ] = response - -    account_ids = Enum.map(res_accounts, & &1["id"]) -    assert length(res_accounts) == 2 -    assert user_two.id in account_ids -    assert user_three.id in account_ids -    assert is_binary(res_id) -    assert unread == true -    assert res_last_status["id"] == direct.id - -    # Apparently undocumented API endpoint -    res_conn = -      conn -      |> assign(:user, user_one) -      |> post("/api/v1/conversations/#{res_id}/read") - -    assert response = json_response(res_conn, 200) -    assert length(response["accounts"]) == 2 -    assert response["last_status"]["id"] == direct.id -    assert response["unread"] == false - -    # (vanilla) Mastodon frontend behaviour -    res_conn = -      conn -      |> assign(:user, user_one) -      |> get("/api/v1/statuses/#{res_last_status["id"]}/context") - -    assert %{"ancestors" => [], "descendants" => []} == json_response(res_conn, 200) -  end - -  test "doesn't include DMs from blocked users", %{conn: conn} do -    blocker = insert(:user) -    blocked = insert(:user) -    user = insert(:user) -    {:ok, blocker} = User.block(blocker, blocked) - -    {:ok, _blocked_direct} = -      CommonAPI.post(blocked, %{ -        "status" => "Hi @#{blocker.nickname}!", -        "visibility" => "direct" -      }) - -    {:ok, direct} = -      CommonAPI.post(user, %{ -        "status" => "Hi @#{blocker.nickname}!", -        "visibility" => "direct" -      }) - -    res_conn = -      conn -      |> assign(:user, user) -      |> get("api/v1/timelines/direct") - -    [status] = json_response(res_conn, 200) -    assert status["id"] == direct.id -  end - -  test "verify_credentials", %{conn: conn} do -    user = insert(:user) - -    conn = -      conn -      |> assign(:user, user) -      |> get("/api/v1/accounts/verify_credentials") - -    response = json_response(conn, 200) - -    assert %{"id" => id, "source" => %{"privacy" => "public"}} = response -    assert response["pleroma"]["chat_token"] -    assert id == to_string(user.id) -  end - -  test "verify_credentials default scope unlisted", %{conn: conn} do -    user = insert(:user, %{info: %User.Info{default_scope: "unlisted"}}) - -    conn = -      conn -      |> assign(:user, user) -      |> get("/api/v1/accounts/verify_credentials") - -    assert %{"id" => id, "source" => %{"privacy" => "unlisted"}} = json_response(conn, 200) -    assert id == to_string(user.id) -  end - -  test "apps/verify_credentials", %{conn: conn} do -    token = insert(:oauth_token) - -    conn = -      conn -      |> assign(:user, token.user) -      |> assign(:token, token) -      |> get("/api/v1/apps/verify_credentials") - -    app = Repo.preload(token, :app).app - -    expected = %{ -      "name" => app.client_name, -      "website" => app.website, -      "vapid_key" => Push.vapid_config() |> Keyword.get(:public_key) -    } - -    assert expected == json_response(conn, 200) -  end - -  test "user avatar can be set", %{conn: conn} do -    user = insert(:user) -    avatar_image = File.read!("test/fixtures/avatar_data_uri") - -    conn = -      conn -      |> assign(:user, user) -      |> patch("/api/v1/pleroma/accounts/update_avatar", %{img: avatar_image}) - -    user = refresh_record(user) - -    assert %{ -             "name" => _, -             "type" => _, -             "url" => [ -               %{ -                 "href" => _, -                 "mediaType" => _, -                 "type" => _ -               } -             ] -           } = user.avatar - -    assert %{"url" => _} = json_response(conn, 200) -  end - -  test "user avatar can be reset", %{conn: conn} do -    user = insert(:user) - -    conn = -      conn -      |> assign(:user, user) -      |> patch("/api/v1/pleroma/accounts/update_avatar", %{img: ""}) - -    user = User.get_cached_by_id(user.id) - -    assert user.avatar == nil - -    assert %{"url" => nil} = json_response(conn, 200) -  end - -  test "can set profile banner", %{conn: conn} do -    user = insert(:user) - -    conn = -      conn -      |> assign(:user, user) -      |> patch("/api/v1/pleroma/accounts/update_banner", %{"banner" => @image}) - -    user = refresh_record(user) -    assert user.info.banner["type"] == "Image" - -    assert %{"url" => _} = json_response(conn, 200) -  end - -  test "can reset profile banner", %{conn: conn} do -    user = insert(:user) - -    conn = -      conn -      |> assign(:user, user) -      |> patch("/api/v1/pleroma/accounts/update_banner", %{"banner" => ""}) - -    user = refresh_record(user) -    assert user.info.banner == %{} - -    assert %{"url" => nil} = json_response(conn, 200) -  end - -  test "background image can be set", %{conn: conn} do -    user = insert(:user) - -    conn = -      conn -      |> assign(:user, user) -      |> patch("/api/v1/pleroma/accounts/update_background", %{"img" => @image}) - -    user = refresh_record(user) -    assert user.info.background["type"] == "Image" -    assert %{"url" => _} = json_response(conn, 200) -  end - -  test "background image can be reset", %{conn: conn} do -    user = insert(:user) - -    conn = -      conn -      |> assign(:user, user) -      |> patch("/api/v1/pleroma/accounts/update_background", %{"img" => ""}) - -    user = refresh_record(user) -    assert user.info.background == %{} -    assert %{"url" => nil} = json_response(conn, 200) -  end - -  test "creates an oauth app", %{conn: conn} do -    user = insert(:user) -    app_attrs = build(:oauth_app) - -    conn = -      conn -      |> assign(:user, user) -      |> post("/api/v1/apps", %{ -        client_name: app_attrs.client_name, -        redirect_uris: app_attrs.redirect_uris -      }) - -    [app] = Repo.all(App) - -    expected = %{ -      "name" => app.client_name, -      "website" => app.website, -      "client_id" => app.client_id, -      "client_secret" => app.client_secret, -      "id" => app.id |> to_string(), -      "redirect_uri" => app.redirect_uris, -      "vapid_key" => Push.vapid_config() |> Keyword.get(:public_key) -    } - -    assert expected == json_response(conn, 200) -  end - -  test "get a status", %{conn: conn} do -    activity = insert(:note_activity) - -    conn = -      conn -      |> get("/api/v1/statuses/#{activity.id}") - -    assert %{"id" => id} = json_response(conn, 200) -    assert id == to_string(activity.id) -  end - -  describe "deleting a status" do -    test "when you created it", %{conn: conn} do -      activity = insert(:note_activity) -      author = User.get_cached_by_ap_id(activity.data["actor"]) - -      conn = -        conn -        |> assign(:user, author) -        |> delete("/api/v1/statuses/#{activity.id}") - -      assert %{} = json_response(conn, 200) - -      refute Activity.get_by_id(activity.id) -    end - -    test "when you didn't create it", %{conn: conn} do -      activity = insert(:note_activity) -      user = insert(:user) - -      conn = -        conn -        |> assign(:user, user) -        |> delete("/api/v1/statuses/#{activity.id}") - -      assert %{"error" => _} = json_response(conn, 403) - -      assert Activity.get_by_id(activity.id) == activity -    end - -    test "when you're an admin or moderator", %{conn: conn} do -      activity1 = insert(:note_activity) -      activity2 = insert(:note_activity) -      admin = insert(:user, info: %{is_admin: true}) -      moderator = insert(:user, info: %{is_moderator: true}) - -      res_conn = -        conn -        |> assign(:user, admin) -        |> delete("/api/v1/statuses/#{activity1.id}") - -      assert %{} = json_response(res_conn, 200) - -      res_conn = -        conn -        |> assign(:user, moderator) -        |> delete("/api/v1/statuses/#{activity2.id}") - -      assert %{} = json_response(res_conn, 200) - -      refute Activity.get_by_id(activity1.id) -      refute Activity.get_by_id(activity2.id) -    end -  end - -  describe "filters" do -    test "creating a filter", %{conn: conn} do -      user = insert(:user) - -      filter = %Pleroma.Filter{ -        phrase: "knights", -        context: ["home"] -      } - -      conn = -        conn -        |> assign(:user, user) -        |> post("/api/v1/filters", %{"phrase" => filter.phrase, context: filter.context}) - -      assert response = json_response(conn, 200) -      assert response["phrase"] == filter.phrase -      assert response["context"] == filter.context -      assert response["irreversible"] == false -      assert response["id"] != nil -      assert response["id"] != "" -    end - -    test "fetching a list of filters", %{conn: conn} do -      user = insert(:user) - -      query_one = %Pleroma.Filter{ -        user_id: user.id, -        filter_id: 1, -        phrase: "knights", -        context: ["home"] -      } - -      query_two = %Pleroma.Filter{ -        user_id: user.id, -        filter_id: 2, -        phrase: "who", -        context: ["home"] -      } - -      {:ok, filter_one} = Pleroma.Filter.create(query_one) -      {:ok, filter_two} = Pleroma.Filter.create(query_two) - -      response = -        conn -        |> assign(:user, user) -        |> get("/api/v1/filters") +        |> get("/api/v1/accounts/#{user.id}/identity_proofs")          |> json_response(200) -      assert response == -               render_json( -                 FilterView, -                 "filters.json", -                 filters: [filter_two, filter_one] -               ) -    end - -    test "get a filter", %{conn: conn} do -      user = insert(:user) - -      query = %Pleroma.Filter{ -        user_id: user.id, -        filter_id: 2, -        phrase: "knight", -        context: ["home"] -      } - -      {:ok, filter} = Pleroma.Filter.create(query) - -      conn = -        conn -        |> assign(:user, user) -        |> get("/api/v1/filters/#{filter.filter_id}") - -      assert _response = json_response(conn, 200) -    end - -    test "update a filter", %{conn: conn} do -      user = insert(:user) - -      query = %Pleroma.Filter{ -        user_id: user.id, -        filter_id: 2, -        phrase: "knight", -        context: ["home"] -      } - -      {:ok, _filter} = Pleroma.Filter.create(query) - -      new = %Pleroma.Filter{ -        phrase: "nii", -        context: ["home"] -      } - -      conn = -        conn -        |> assign(:user, user) -        |> put("/api/v1/filters/#{query.filter_id}", %{ -          phrase: new.phrase, -          context: new.context -        }) - -      assert response = json_response(conn, 200) -      assert response["phrase"] == new.phrase -      assert response["context"] == new.context -    end - -    test "delete a filter", %{conn: conn} do -      user = insert(:user) - -      query = %Pleroma.Filter{ -        user_id: user.id, -        filter_id: 2, -        phrase: "knight", -        context: ["home"] -      } - -      {:ok, filter} = Pleroma.Filter.create(query) - -      conn = -        conn -        |> assign(:user, user) -        |> delete("/api/v1/filters/#{filter.filter_id}") - -      assert response = json_response(conn, 200) -      assert response == %{} -    end -  end - -  describe "lists" do -    test "creating a list", %{conn: conn} do -      user = insert(:user) - -      conn = -        conn -        |> assign(:user, user) -        |> post("/api/v1/lists", %{"title" => "cuties"}) - -      assert %{"title" => title} = json_response(conn, 200) -      assert title == "cuties" -    end - -    test "adding users to a list", %{conn: conn} do -      user = insert(:user) -      other_user = insert(:user) -      {:ok, list} = Pleroma.List.create("name", user) - -      conn = -        conn -        |> assign(:user, user) -        |> post("/api/v1/lists/#{list.id}/accounts", %{"account_ids" => [other_user.id]}) - -      assert %{} == json_response(conn, 200) -      %Pleroma.List{following: following} = Pleroma.List.get(list.id, user) -      assert following == [other_user.follower_address] -    end - -    test "removing users from a list", %{conn: conn} do -      user = insert(:user) -      other_user = insert(:user) -      third_user = insert(:user) -      {:ok, list} = Pleroma.List.create("name", user) -      {:ok, list} = Pleroma.List.follow(list, other_user) -      {:ok, list} = Pleroma.List.follow(list, third_user) - -      conn = -        conn -        |> assign(:user, user) -        |> delete("/api/v1/lists/#{list.id}/accounts", %{"account_ids" => [other_user.id]}) - -      assert %{} == json_response(conn, 200) -      %Pleroma.List{following: following} = Pleroma.List.get(list.id, user) -      assert following == [third_user.follower_address] -    end - -    test "listing users in a list", %{conn: conn} do -      user = insert(:user) -      other_user = insert(:user) -      {:ok, list} = Pleroma.List.create("name", user) -      {:ok, list} = Pleroma.List.follow(list, other_user) - -      conn = -        conn -        |> assign(:user, user) -        |> get("/api/v1/lists/#{list.id}/accounts", %{"account_ids" => [other_user.id]}) - -      assert [%{"id" => id}] = json_response(conn, 200) -      assert id == to_string(other_user.id) -    end - -    test "retrieving a list", %{conn: conn} do -      user = insert(:user) -      {:ok, list} = Pleroma.List.create("name", user) - -      conn = -        conn -        |> assign(:user, user) -        |> get("/api/v1/lists/#{list.id}") - -      assert %{"id" => id} = json_response(conn, 200) -      assert id == to_string(list.id) -    end - -    test "renaming a list", %{conn: conn} do -      user = insert(:user) -      {:ok, list} = Pleroma.List.create("name", user) - -      conn = -        conn -        |> assign(:user, user) -        |> put("/api/v1/lists/#{list.id}", %{"title" => "newname"}) - -      assert %{"title" => name} = json_response(conn, 200) -      assert name == "newname" -    end - -    test "deleting a list", %{conn: conn} do -      user = insert(:user) -      {:ok, list} = Pleroma.List.create("name", user) - -      conn = -        conn -        |> assign(:user, user) -        |> delete("/api/v1/lists/#{list.id}") - -      assert %{} = json_response(conn, 200) -      assert is_nil(Repo.get(Pleroma.List, list.id)) -    end - -    test "list timeline", %{conn: conn} do -      user = insert(:user) -      other_user = insert(:user) -      {:ok, _activity_one} = CommonAPI.post(user, %{"status" => "Marisa is cute."}) -      {:ok, activity_two} = CommonAPI.post(other_user, %{"status" => "Marisa is cute."}) -      {:ok, list} = Pleroma.List.create("name", user) -      {:ok, list} = Pleroma.List.follow(list, other_user) - -      conn = -        conn -        |> assign(:user, user) -        |> get("/api/v1/timelines/list/#{list.id}") - -      assert [%{"id" => id}] = json_response(conn, 200) - -      assert id == to_string(activity_two.id) -    end - -    test "list timeline does not leak non-public statuses for unfollowed users", %{conn: conn} do -      user = insert(:user) -      other_user = insert(:user) -      {:ok, activity_one} = CommonAPI.post(other_user, %{"status" => "Marisa is cute."}) - -      {:ok, _activity_two} = -        CommonAPI.post(other_user, %{ -          "status" => "Marisa is cute.", -          "visibility" => "private" -        }) - -      {:ok, list} = Pleroma.List.create("name", user) -      {:ok, list} = Pleroma.List.follow(list, other_user) - -      conn = -        conn -        |> assign(:user, user) -        |> get("/api/v1/timelines/list/#{list.id}") - -      assert [%{"id" => id}] = json_response(conn, 200) - -      assert id == to_string(activity_one.id) -    end -  end - -  describe "notifications" do -    test "list of notifications", %{conn: conn} do -      user = insert(:user) -      other_user = insert(:user) - -      {:ok, activity} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"}) - -      {:ok, [_notification]} = Notification.create_notifications(activity) - -      conn = -        conn -        |> assign(:user, user) -        |> get("/api/v1/notifications") - -      expected_response = -        "hi <span class=\"h-card\"><a data-user=\"#{user.id}\" class=\"u-url mention\" href=\"#{ -          user.ap_id -        }\">@<span>#{user.nickname}</span></a></span>" - -      assert [%{"status" => %{"content" => response}} | _rest] = json_response(conn, 200) -      assert response == expected_response -    end - -    test "getting a single notification", %{conn: conn} do -      user = insert(:user) -      other_user = insert(:user) - -      {:ok, activity} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"}) - -      {:ok, [notification]} = Notification.create_notifications(activity) - -      conn = -        conn -        |> assign(:user, user) -        |> get("/api/v1/notifications/#{notification.id}") - -      expected_response = -        "hi <span class=\"h-card\"><a data-user=\"#{user.id}\" class=\"u-url mention\" href=\"#{ -          user.ap_id -        }\">@<span>#{user.nickname}</span></a></span>" - -      assert %{"status" => %{"content" => response}} = json_response(conn, 200) -      assert response == expected_response -    end - -    test "dismissing a single notification", %{conn: conn} do -      user = insert(:user) -      other_user = insert(:user) - -      {:ok, activity} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"}) - -      {:ok, [notification]} = Notification.create_notifications(activity) - -      conn = -        conn -        |> assign(:user, user) -        |> post("/api/v1/notifications/dismiss", %{"id" => notification.id}) - -      assert %{} = json_response(conn, 200) -    end - -    test "clearing all notifications", %{conn: conn} do -      user = insert(:user) -      other_user = insert(:user) - -      {:ok, activity} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"}) - -      {:ok, [_notification]} = Notification.create_notifications(activity) - -      conn = -        conn -        |> assign(:user, user) -        |> post("/api/v1/notifications/clear") - -      assert %{} = json_response(conn, 200) - -      conn = -        build_conn() -        |> assign(:user, user) -        |> get("/api/v1/notifications") - -      assert all = json_response(conn, 200) -      assert all == [] -    end - -    test "paginates notifications using min_id, since_id, max_id, and limit", %{conn: conn} do -      user = insert(:user) -      other_user = insert(:user) - -      {:ok, activity1} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"}) -      {:ok, activity2} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"}) -      {:ok, activity3} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"}) -      {:ok, activity4} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"}) - -      notification1_id = Repo.get_by(Notification, activity_id: activity1.id).id |> to_string() -      notification2_id = Repo.get_by(Notification, activity_id: activity2.id).id |> to_string() -      notification3_id = Repo.get_by(Notification, activity_id: activity3.id).id |> to_string() -      notification4_id = Repo.get_by(Notification, activity_id: activity4.id).id |> to_string() - -      conn = -        conn -        |> assign(:user, user) - -      # min_id -      conn_res = -        conn -        |> get("/api/v1/notifications?limit=2&min_id=#{notification1_id}") - -      result = json_response(conn_res, 200) -      assert [%{"id" => ^notification3_id}, %{"id" => ^notification2_id}] = result - -      # since_id -      conn_res = -        conn -        |> get("/api/v1/notifications?limit=2&since_id=#{notification1_id}") - -      result = json_response(conn_res, 200) -      assert [%{"id" => ^notification4_id}, %{"id" => ^notification3_id}] = result - -      # max_id -      conn_res = -        conn -        |> get("/api/v1/notifications?limit=2&max_id=#{notification4_id}") - -      result = json_response(conn_res, 200) -      assert [%{"id" => ^notification3_id}, %{"id" => ^notification2_id}] = result -    end - -    test "filters notifications using exclude_types", %{conn: conn} do -      user = insert(:user) -      other_user = insert(:user) - -      {:ok, mention_activity} = CommonAPI.post(other_user, %{"status" => "hey @#{user.nickname}"}) -      {:ok, create_activity} = CommonAPI.post(user, %{"status" => "hey"}) -      {:ok, favorite_activity, _} = CommonAPI.favorite(create_activity.id, other_user) -      {:ok, reblog_activity, _} = CommonAPI.repeat(create_activity.id, other_user) -      {:ok, _, _, follow_activity} = CommonAPI.follow(other_user, user) - -      mention_notification_id = -        Repo.get_by(Notification, activity_id: mention_activity.id).id |> to_string() - -      favorite_notification_id = -        Repo.get_by(Notification, activity_id: favorite_activity.id).id |> to_string() - -      reblog_notification_id = -        Repo.get_by(Notification, activity_id: reblog_activity.id).id |> to_string() - -      follow_notification_id = -        Repo.get_by(Notification, activity_id: follow_activity.id).id |> to_string() - -      conn = -        conn -        |> assign(:user, user) - -      conn_res = -        get(conn, "/api/v1/notifications", %{exclude_types: ["mention", "favourite", "reblog"]}) - -      assert [%{"id" => ^follow_notification_id}] = json_response(conn_res, 200) - -      conn_res = -        get(conn, "/api/v1/notifications", %{exclude_types: ["favourite", "reblog", "follow"]}) - -      assert [%{"id" => ^mention_notification_id}] = json_response(conn_res, 200) - -      conn_res = -        get(conn, "/api/v1/notifications", %{exclude_types: ["reblog", "follow", "mention"]}) - -      assert [%{"id" => ^favorite_notification_id}] = json_response(conn_res, 200) - -      conn_res = -        get(conn, "/api/v1/notifications", %{exclude_types: ["follow", "mention", "favourite"]}) - -      assert [%{"id" => ^reblog_notification_id}] = json_response(conn_res, 200) -    end - -    test "destroy multiple", %{conn: conn} do -      user = insert(:user) -      other_user = insert(:user) - -      {:ok, activity1} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"}) -      {:ok, activity2} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"}) -      {:ok, activity3} = CommonAPI.post(user, %{"status" => "hi @#{other_user.nickname}"}) -      {:ok, activity4} = CommonAPI.post(user, %{"status" => "hi @#{other_user.nickname}"}) - -      notification1_id = Repo.get_by(Notification, activity_id: activity1.id).id |> to_string() -      notification2_id = Repo.get_by(Notification, activity_id: activity2.id).id |> to_string() -      notification3_id = Repo.get_by(Notification, activity_id: activity3.id).id |> to_string() -      notification4_id = Repo.get_by(Notification, activity_id: activity4.id).id |> to_string() - -      conn = -        conn -        |> assign(:user, user) - -      conn_res = -        conn -        |> get("/api/v1/notifications") - -      result = json_response(conn_res, 200) -      assert [%{"id" => ^notification2_id}, %{"id" => ^notification1_id}] = result - -      conn2 = -        conn -        |> assign(:user, other_user) - -      conn_res = -        conn2 -        |> get("/api/v1/notifications") - -      result = json_response(conn_res, 200) -      assert [%{"id" => ^notification4_id}, %{"id" => ^notification3_id}] = result - -      conn_destroy = -        conn -        |> delete("/api/v1/notifications/destroy_multiple", %{ -          "ids" => [notification1_id, notification2_id] -        }) - -      assert json_response(conn_destroy, 200) == %{} - -      conn_res = -        conn2 -        |> get("/api/v1/notifications") - -      result = json_response(conn_res, 200) -      assert [%{"id" => ^notification4_id}, %{"id" => ^notification3_id}] = result +      assert res == []      end -    test "doesn't see notifications after muting user with notifications", %{conn: conn} do -      user = insert(:user) -      user2 = insert(:user) - -      {:ok, _, _, _} = CommonAPI.follow(user, user2) -      {:ok, _} = CommonAPI.post(user2, %{"status" => "hey @#{user.nickname}"}) - -      conn = assign(conn, :user, user) - -      conn = get(conn, "/api/v1/notifications") - -      assert length(json_response(conn, 200)) == 1 - -      {:ok, user} = User.mute(user, user2) - -      conn = assign(build_conn(), :user, user) -      conn = get(conn, "/api/v1/notifications") - -      assert json_response(conn, 200) == [] -    end - -    test "see notifications after muting user without notifications", %{conn: conn} do -      user = insert(:user) -      user2 = insert(:user) - -      {:ok, _, _, _} = CommonAPI.follow(user, user2) -      {:ok, _} = CommonAPI.post(user2, %{"status" => "hey @#{user.nickname}"}) - -      conn = assign(conn, :user, user) - -      conn = get(conn, "/api/v1/notifications") - -      assert length(json_response(conn, 200)) == 1 - -      {:ok, user} = User.mute(user, user2, false) - -      conn = assign(build_conn(), :user, user) -      conn = get(conn, "/api/v1/notifications") - -      assert length(json_response(conn, 200)) == 1 -    end - -    test "see notifications after muting user with notifications and with_muted parameter", %{ -      conn: conn -    } do -      user = insert(:user) -      user2 = insert(:user) - -      {:ok, _, _, _} = CommonAPI.follow(user, user2) -      {:ok, _} = CommonAPI.post(user2, %{"status" => "hey @#{user.nickname}"}) - -      conn = assign(conn, :user, user) - -      conn = get(conn, "/api/v1/notifications") - -      assert length(json_response(conn, 200)) == 1 - -      {:ok, user} = User.mute(user, user2) - -      conn = assign(build_conn(), :user, user) -      conn = get(conn, "/api/v1/notifications", %{"with_muted" => "true"}) +    test "GET /api/v1/endorsements" do +      %{conn: conn} = oauth_access(["read:accounts"]) -      assert length(json_response(conn, 200)) == 1 -    end -  end - -  describe "reblogging" do -    test "reblogs and returns the reblogged status", %{conn: conn} do -      activity = insert(:note_activity) -      user = insert(:user) - -      conn = -        conn -        |> assign(:user, user) -        |> post("/api/v1/statuses/#{activity.id}/reblog") - -      assert %{ -               "reblog" => %{"id" => id, "reblogged" => true, "reblogs_count" => 1}, -               "reblogged" => true -             } = json_response(conn, 200) - -      assert to_string(activity.id) == id -    end - -    test "reblogged status for another user", %{conn: conn} do -      activity = insert(:note_activity) -      user1 = insert(:user) -      user2 = insert(:user) -      user3 = insert(:user) -      CommonAPI.favorite(activity.id, user2) -      {:ok, _bookmark} = Pleroma.Bookmark.create(user2.id, activity.id) -      {:ok, reblog_activity1, _object} = CommonAPI.repeat(activity.id, user1) -      {:ok, _, _object} = CommonAPI.repeat(activity.id, user2) - -      conn_res = -        conn -        |> assign(:user, user3) -        |> get("/api/v1/statuses/#{reblog_activity1.id}") - -      assert %{ -               "reblog" => %{"id" => id, "reblogged" => false, "reblogs_count" => 2}, -               "reblogged" => false, -               "favourited" => false, -               "bookmarked" => false -             } = json_response(conn_res, 200) - -      conn_res = -        conn -        |> assign(:user, user2) -        |> get("/api/v1/statuses/#{reblog_activity1.id}") - -      assert %{ -               "reblog" => %{"id" => id, "reblogged" => true, "reblogs_count" => 2}, -               "reblogged" => true, -               "favourited" => true, -               "bookmarked" => true -             } = json_response(conn_res, 200) - -      assert to_string(activity.id) == id -    end - -    test "returns 400 error when activity is not exist", %{conn: conn} do -      user = insert(:user) - -      conn = +      res =          conn -        |> assign(:user, user) -        |> post("/api/v1/statuses/foo/reblog") - -      assert json_response(conn, 400) == %{"error" => "Could not repeat"} -    end -  end - -  describe "unreblogging" do -    test "unreblogs and returns the unreblogged status", %{conn: conn} do -      activity = insert(:note_activity) -      user = insert(:user) - -      {:ok, _, _} = CommonAPI.repeat(activity.id, user) - -      conn = -        conn -        |> assign(:user, user) -        |> post("/api/v1/statuses/#{activity.id}/unreblog") - -      assert %{"id" => id, "reblogged" => false, "reblogs_count" => 0} = json_response(conn, 200) - -      assert to_string(activity.id) == id -    end - -    test "returns 400 error when activity is not exist", %{conn: conn} do -      user = insert(:user) - -      conn = -        conn -        |> assign(:user, user) -        |> post("/api/v1/statuses/foo/unreblog") - -      assert json_response(conn, 400) == %{"error" => "Could not unrepeat"} -    end -  end - -  describe "favoriting" do -    test "favs a status and returns it", %{conn: conn} do -      activity = insert(:note_activity) -      user = insert(:user) - -      conn = -        conn -        |> assign(:user, user) -        |> post("/api/v1/statuses/#{activity.id}/favourite") - -      assert %{"id" => id, "favourites_count" => 1, "favourited" => true} = -               json_response(conn, 200) - -      assert to_string(activity.id) == id -    end - -    test "returns 400 error for a wrong id", %{conn: conn} do -      user = insert(:user) - -      conn = -        conn -        |> assign(:user, user) -        |> post("/api/v1/statuses/1/favourite") - -      assert json_response(conn, 400) == %{"error" => "Could not favorite"} -    end -  end - -  describe "unfavoriting" do -    test "unfavorites a status and returns it", %{conn: conn} do -      activity = insert(:note_activity) -      user = insert(:user) - -      {:ok, _, _} = CommonAPI.favorite(activity.id, user) - -      conn = -        conn -        |> assign(:user, user) -        |> post("/api/v1/statuses/#{activity.id}/unfavourite") - -      assert %{"id" => id, "favourites_count" => 0, "favourited" => false} = -               json_response(conn, 200) - -      assert to_string(activity.id) == id -    end - -    test "returns 400 error for a wrong id", %{conn: conn} do -      user = insert(:user) - -      conn = -        conn -        |> assign(:user, user) -        |> post("/api/v1/statuses/1/unfavourite") - -      assert json_response(conn, 400) == %{"error" => "Could not unfavorite"} -    end -  end - -  describe "user timelines" do -    test "gets a users statuses", %{conn: conn} do -      user_one = insert(:user) -      user_two = insert(:user) -      user_three = insert(:user) - -      {:ok, user_three} = User.follow(user_three, user_one) - -      {:ok, activity} = CommonAPI.post(user_one, %{"status" => "HI!!!"}) - -      {:ok, direct_activity} = -        CommonAPI.post(user_one, %{ -          "status" => "Hi, @#{user_two.nickname}.", -          "visibility" => "direct" -        }) - -      {:ok, private_activity} = -        CommonAPI.post(user_one, %{"status" => "private", "visibility" => "private"}) - -      resp = -        conn -        |> get("/api/v1/accounts/#{user_one.id}/statuses") - -      assert [%{"id" => id}] = json_response(resp, 200) -      assert id == to_string(activity.id) - -      resp = -        conn -        |> assign(:user, user_two) -        |> get("/api/v1/accounts/#{user_one.id}/statuses") - -      assert [%{"id" => id_one}, %{"id" => id_two}] = json_response(resp, 200) -      assert id_one == to_string(direct_activity.id) -      assert id_two == to_string(activity.id) - -      resp = -        conn -        |> assign(:user, user_three) -        |> get("/api/v1/accounts/#{user_one.id}/statuses") - -      assert [%{"id" => id_one}, %{"id" => id_two}] = json_response(resp, 200) -      assert id_one == to_string(private_activity.id) -      assert id_two == to_string(activity.id) -    end - -    test "unimplemented pinned statuses feature", %{conn: conn} do -      note = insert(:note_activity) -      user = User.get_cached_by_ap_id(note.data["actor"]) - -      conn = -        conn -        |> get("/api/v1/accounts/#{user.id}/statuses?pinned=true") - -      assert json_response(conn, 200) == [] -    end - -    test "gets an users media", %{conn: conn} do -      note = insert(:note_activity) -      user = User.get_cached_by_ap_id(note.data["actor"]) - -      file = %Plug.Upload{ -        content_type: "image/jpg", -        path: Path.absname("test/fixtures/image.jpg"), -        filename: "an_image.jpg" -      } - -      media = -        TwitterAPI.upload(file, user, "json") -        |> Jason.decode!() - -      {:ok, image_post} = -        CommonAPI.post(user, %{"status" => "cofe", "media_ids" => [media["media_id"]]}) - -      conn = -        conn -        |> get("/api/v1/accounts/#{user.id}/statuses", %{"only_media" => "true"}) - -      assert [%{"id" => id}] = json_response(conn, 200) -      assert id == to_string(image_post.id) - -      conn = -        build_conn() -        |> get("/api/v1/accounts/#{user.id}/statuses", %{"only_media" => "1"}) - -      assert [%{"id" => id}] = json_response(conn, 200) -      assert id == to_string(image_post.id) -    end - -    test "gets a user's statuses without reblogs", %{conn: conn} do -      user = insert(:user) -      {:ok, post} = CommonAPI.post(user, %{"status" => "HI!!!"}) -      {:ok, _, _} = CommonAPI.repeat(post.id, user) - -      conn = -        conn -        |> get("/api/v1/accounts/#{user.id}/statuses", %{"exclude_reblogs" => "true"}) - -      assert [%{"id" => id}] = json_response(conn, 200) -      assert id == to_string(post.id) - -      conn = -        conn -        |> get("/api/v1/accounts/#{user.id}/statuses", %{"exclude_reblogs" => "1"}) - -      assert [%{"id" => id}] = json_response(conn, 200) -      assert id == to_string(post.id) -    end - -    test "filters user's statuses by a hashtag", %{conn: conn} do -      user = insert(:user) -      {:ok, post} = CommonAPI.post(user, %{"status" => "#hashtag"}) -      {:ok, _post} = CommonAPI.post(user, %{"status" => "hashtag"}) - -      conn = -        conn -        |> get("/api/v1/accounts/#{user.id}/statuses", %{"tagged" => "hashtag"}) - -      assert [%{"id" => id}] = json_response(conn, 200) -      assert id == to_string(post.id) -    end -  end - -  describe "user relationships" do -    test "returns the relationships for the current user", %{conn: conn} do -      user = insert(:user) -      other_user = insert(:user) -      {:ok, user} = User.follow(user, other_user) - -      conn = -        conn -        |> assign(:user, user) -        |> get("/api/v1/accounts/relationships", %{"id" => [other_user.id]}) - -      assert [relationship] = json_response(conn, 200) - -      assert to_string(other_user.id) == relationship["id"] -    end -  end - -  describe "media upload" do -    setup do -      upload_config = Pleroma.Config.get([Pleroma.Upload]) -      proxy_config = Pleroma.Config.get([:media_proxy]) - -      on_exit(fn -> -        Pleroma.Config.put([Pleroma.Upload], upload_config) -        Pleroma.Config.put([:media_proxy], proxy_config) -      end) - -      user = insert(:user) - -      conn = -        build_conn() -        |> assign(:user, user) - -      image = %Plug.Upload{ -        content_type: "image/jpg", -        path: Path.absname("test/fixtures/image.jpg"), -        filename: "an_image.jpg" -      } - -      [conn: conn, image: image] -    end - -    test "returns uploaded image", %{conn: conn, image: image} do -      desc = "Description of the image" - -      media = -        conn -        |> post("/api/v1/media", %{"file" => image, "description" => desc}) -        |> json_response(:ok) - -      assert media["type"] == "image" -      assert media["description"] == desc -      assert media["id"] - -      object = Repo.get(Object, media["id"]) -      assert object.data["actor"] == User.ap_id(conn.assigns[:user]) -    end - -    test "returns proxied url when media proxy is enabled", %{conn: conn, image: image} do -      Pleroma.Config.put([Pleroma.Upload, :base_url], "https://media.pleroma.social") - -      proxy_url = "https://cache.pleroma.social" -      Pleroma.Config.put([:media_proxy, :enabled], true) -      Pleroma.Config.put([:media_proxy, :base_url], proxy_url) - -      media = -        conn -        |> post("/api/v1/media", %{"file" => image}) -        |> json_response(:ok) - -      assert String.starts_with?(media["url"], proxy_url) -    end - -    test "returns media url when proxy is enabled but media url is whitelisted", %{ -      conn: conn, -      image: image -    } do -      media_url = "https://media.pleroma.social" -      Pleroma.Config.put([Pleroma.Upload, :base_url], media_url) - -      Pleroma.Config.put([:media_proxy, :enabled], true) -      Pleroma.Config.put([:media_proxy, :base_url], "https://cache.pleroma.social") -      Pleroma.Config.put([:media_proxy, :whitelist], ["media.pleroma.social"]) - -      media = -        conn -        |> post("/api/v1/media", %{"file" => image}) -        |> json_response(:ok) - -      assert String.starts_with?(media["url"], media_url) -    end -  end - -  describe "locked accounts" do -    test "/api/v1/follow_requests works" do -      user = insert(:user, %{info: %User.Info{locked: true}}) -      other_user = insert(:user) - -      {:ok, _activity} = ActivityPub.follow(other_user, user) - -      user = User.get_cached_by_id(user.id) -      other_user = User.get_cached_by_id(other_user.id) - -      assert User.following?(other_user, user) == false - -      conn = -        build_conn() -        |> assign(:user, user) -        |> get("/api/v1/follow_requests") - -      assert [relationship] = json_response(conn, 200) -      assert to_string(other_user.id) == relationship["id"] -    end - -    test "/api/v1/follow_requests/:id/authorize works" do -      user = insert(:user, %{info: %User.Info{locked: true}}) -      other_user = insert(:user) - -      {:ok, _activity} = ActivityPub.follow(other_user, user) - -      user = User.get_cached_by_id(user.id) -      other_user = User.get_cached_by_id(other_user.id) - -      assert User.following?(other_user, user) == false - -      conn = -        build_conn() -        |> assign(:user, user) -        |> post("/api/v1/follow_requests/#{other_user.id}/authorize") - -      assert relationship = json_response(conn, 200) -      assert to_string(other_user.id) == relationship["id"] - -      user = User.get_cached_by_id(user.id) -      other_user = User.get_cached_by_id(other_user.id) - -      assert User.following?(other_user, user) == true -    end - -    test "verify_credentials", %{conn: conn} do -      user = insert(:user, %{info: %User.Info{default_scope: "private"}}) - -      conn = -        conn -        |> assign(:user, user) -        |> get("/api/v1/accounts/verify_credentials") - -      assert %{"id" => id, "source" => %{"privacy" => "private"}} = json_response(conn, 200) -      assert id == to_string(user.id) -    end - -    test "/api/v1/follow_requests/:id/reject works" do -      user = insert(:user, %{info: %User.Info{locked: true}}) -      other_user = insert(:user) - -      {:ok, _activity} = ActivityPub.follow(other_user, user) - -      user = User.get_cached_by_id(user.id) - -      conn = -        build_conn() -        |> assign(:user, user) -        |> post("/api/v1/follow_requests/#{other_user.id}/reject") - -      assert relationship = json_response(conn, 200) -      assert to_string(other_user.id) == relationship["id"] - -      user = User.get_cached_by_id(user.id) -      other_user = User.get_cached_by_id(other_user.id) - -      assert User.following?(other_user, user) == false -    end -  end - -  test "account fetching", %{conn: conn} do -    user = insert(:user) - -    conn = -      conn -      |> get("/api/v1/accounts/#{user.id}") - -    assert %{"id" => id} = json_response(conn, 200) -    assert id == to_string(user.id) - -    conn = -      build_conn() -      |> get("/api/v1/accounts/-1") - -    assert %{"error" => "Can't find user"} = json_response(conn, 404) -  end - -  test "account fetching also works nickname", %{conn: conn} do -    user = insert(:user) - -    conn = -      conn -      |> get("/api/v1/accounts/#{user.nickname}") - -    assert %{"id" => id} = json_response(conn, 200) -    assert id == user.id -  end - -  test "mascot upload", %{conn: conn} do -    user = insert(:user) - -    non_image_file = %Plug.Upload{ -      content_type: "audio/mpeg", -      path: Path.absname("test/fixtures/sound.mp3"), -      filename: "sound.mp3" -    } - -    conn = -      conn -      |> assign(:user, user) -      |> put("/api/v1/pleroma/mascot", %{"file" => non_image_file}) - -    assert json_response(conn, 415) - -    file = %Plug.Upload{ -      content_type: "image/jpg", -      path: Path.absname("test/fixtures/image.jpg"), -      filename: "an_image.jpg" -    } - -    conn = -      build_conn() -      |> assign(:user, user) -      |> put("/api/v1/pleroma/mascot", %{"file" => file}) - -    assert %{"id" => _, "type" => image} = json_response(conn, 200) -  end - -  test "mascot retrieving", %{conn: conn} do -    user = insert(:user) -    # When user hasn't set a mascot, we should just get pleroma tan back -    conn = -      conn -      |> assign(:user, user) -      |> get("/api/v1/pleroma/mascot") - -    assert %{"url" => url} = json_response(conn, 200) -    assert url =~ "pleroma-fox-tan-smol" - -    # When a user sets their mascot, we should get that back -    file = %Plug.Upload{ -      content_type: "image/jpg", -      path: Path.absname("test/fixtures/image.jpg"), -      filename: "an_image.jpg" -    } - -    conn = -      build_conn() -      |> assign(:user, user) -      |> put("/api/v1/pleroma/mascot", %{"file" => file}) - -    assert json_response(conn, 200) - -    user = User.get_cached_by_id(user.id) - -    conn = -      build_conn() -      |> assign(:user, user) -      |> get("/api/v1/pleroma/mascot") - -    assert %{"url" => url, "type" => "image"} = json_response(conn, 200) -    assert url =~ "an_image" -  end - -  test "hashtag timeline", %{conn: conn} do -    following = insert(:user) - -    capture_log(fn -> -      {:ok, activity} = CommonAPI.post(following, %{"status" => "test #2hu"}) - -      {:ok, [_activity]} = -        OStatus.fetch_activity_from_url("https://shitposter.club/notice/2827873") - -      nconn = -        conn -        |> get("/api/v1/timelines/tag/2hu") - -      assert [%{"id" => id}] = json_response(nconn, 200) - -      assert id == to_string(activity.id) - -      # works for different capitalization too -      nconn = -        conn -        |> get("/api/v1/timelines/tag/2HU") - -      assert [%{"id" => id}] = json_response(nconn, 200) - -      assert id == to_string(activity.id) -    end) -  end - -  test "multi-hashtag timeline", %{conn: conn} do -    user = insert(:user) - -    {:ok, activity_test} = CommonAPI.post(user, %{"status" => "#test"}) -    {:ok, activity_test1} = CommonAPI.post(user, %{"status" => "#test #test1"}) -    {:ok, activity_none} = CommonAPI.post(user, %{"status" => "#test #none"}) - -    any_test = -      conn -      |> get("/api/v1/timelines/tag/test", %{"any" => ["test1"]}) - -    [status_none, status_test1, status_test] = json_response(any_test, 200) - -    assert to_string(activity_test.id) == status_test["id"] -    assert to_string(activity_test1.id) == status_test1["id"] -    assert to_string(activity_none.id) == status_none["id"] - -    restricted_test = -      conn -      |> get("/api/v1/timelines/tag/test", %{"all" => ["test1"], "none" => ["none"]}) - -    assert [status_test1] == json_response(restricted_test, 200) - -    all_test = conn |> get("/api/v1/timelines/tag/test", %{"all" => ["none"]}) - -    assert [status_none] == json_response(all_test, 200) -  end - -  test "getting followers", %{conn: conn} do -    user = insert(:user) -    other_user = insert(:user) -    {:ok, user} = User.follow(user, other_user) - -    conn = -      conn -      |> get("/api/v1/accounts/#{other_user.id}/followers") - -    assert [%{"id" => id}] = json_response(conn, 200) -    assert id == to_string(user.id) -  end - -  test "getting followers, hide_followers", %{conn: conn} do -    user = insert(:user) -    other_user = insert(:user, %{info: %{hide_followers: true}}) -    {:ok, _user} = User.follow(user, other_user) - -    conn = -      conn -      |> get("/api/v1/accounts/#{other_user.id}/followers") - -    assert [] == json_response(conn, 200) -  end - -  test "getting followers, hide_followers, same user requesting", %{conn: conn} do -    user = insert(:user) -    other_user = insert(:user, %{info: %{hide_followers: true}}) -    {:ok, _user} = User.follow(user, other_user) - -    conn = -      conn -      |> assign(:user, other_user) -      |> get("/api/v1/accounts/#{other_user.id}/followers") - -    refute [] == json_response(conn, 200) -  end - -  test "getting followers, pagination", %{conn: conn} do -    user = insert(:user) -    follower1 = insert(:user) -    follower2 = insert(:user) -    follower3 = insert(:user) -    {:ok, _} = User.follow(follower1, user) -    {:ok, _} = User.follow(follower2, user) -    {:ok, _} = User.follow(follower3, user) - -    conn = -      conn -      |> assign(:user, user) - -    res_conn = -      conn -      |> get("/api/v1/accounts/#{user.id}/followers?since_id=#{follower1.id}") - -    assert [%{"id" => id3}, %{"id" => id2}] = json_response(res_conn, 200) -    assert id3 == follower3.id -    assert id2 == follower2.id - -    res_conn = -      conn -      |> get("/api/v1/accounts/#{user.id}/followers?max_id=#{follower3.id}") - -    assert [%{"id" => id2}, %{"id" => id1}] = json_response(res_conn, 200) -    assert id2 == follower2.id -    assert id1 == follower1.id - -    res_conn = -      conn -      |> get("/api/v1/accounts/#{user.id}/followers?limit=1&max_id=#{follower3.id}") - -    assert [%{"id" => id2}] = json_response(res_conn, 200) -    assert id2 == follower2.id - -    assert [link_header] = get_resp_header(res_conn, "link") -    assert link_header =~ ~r/min_id=#{follower2.id}/ -    assert link_header =~ ~r/max_id=#{follower2.id}/ -  end - -  test "getting following", %{conn: conn} do -    user = insert(:user) -    other_user = insert(:user) -    {:ok, user} = User.follow(user, other_user) - -    conn = -      conn -      |> get("/api/v1/accounts/#{user.id}/following") - -    assert [%{"id" => id}] = json_response(conn, 200) -    assert id == to_string(other_user.id) -  end - -  test "getting following, hide_follows", %{conn: conn} do -    user = insert(:user, %{info: %{hide_follows: true}}) -    other_user = insert(:user) -    {:ok, user} = User.follow(user, other_user) - -    conn = -      conn -      |> get("/api/v1/accounts/#{user.id}/following") - -    assert [] == json_response(conn, 200) -  end - -  test "getting following, hide_follows, same user requesting", %{conn: conn} do -    user = insert(:user, %{info: %{hide_follows: true}}) -    other_user = insert(:user) -    {:ok, user} = User.follow(user, other_user) - -    conn = -      conn -      |> assign(:user, user) -      |> get("/api/v1/accounts/#{user.id}/following") - -    refute [] == json_response(conn, 200) -  end - -  test "getting following, pagination", %{conn: conn} do -    user = insert(:user) -    following1 = insert(:user) -    following2 = insert(:user) -    following3 = insert(:user) -    {:ok, _} = User.follow(user, following1) -    {:ok, _} = User.follow(user, following2) -    {:ok, _} = User.follow(user, following3) - -    conn = -      conn -      |> assign(:user, user) - -    res_conn = -      conn -      |> get("/api/v1/accounts/#{user.id}/following?since_id=#{following1.id}") - -    assert [%{"id" => id3}, %{"id" => id2}] = json_response(res_conn, 200) -    assert id3 == following3.id -    assert id2 == following2.id - -    res_conn = -      conn -      |> get("/api/v1/accounts/#{user.id}/following?max_id=#{following3.id}") - -    assert [%{"id" => id2}, %{"id" => id1}] = json_response(res_conn, 200) -    assert id2 == following2.id -    assert id1 == following1.id - -    res_conn = -      conn -      |> get("/api/v1/accounts/#{user.id}/following?limit=1&max_id=#{following3.id}") - -    assert [%{"id" => id2}] = json_response(res_conn, 200) -    assert id2 == following2.id - -    assert [link_header] = get_resp_header(res_conn, "link") -    assert link_header =~ ~r/min_id=#{following2.id}/ -    assert link_header =~ ~r/max_id=#{following2.id}/ -  end - -  test "following / unfollowing a user", %{conn: conn} do -    user = insert(:user) -    other_user = insert(:user) - -    conn = -      conn -      |> assign(:user, user) -      |> post("/api/v1/accounts/#{other_user.id}/follow") - -    assert %{"id" => _id, "following" => true} = json_response(conn, 200) - -    user = User.get_cached_by_id(user.id) - -    conn = -      build_conn() -      |> assign(:user, user) -      |> post("/api/v1/accounts/#{other_user.id}/unfollow") - -    assert %{"id" => _id, "following" => false} = json_response(conn, 200) - -    user = User.get_cached_by_id(user.id) - -    conn = -      build_conn() -      |> assign(:user, user) -      |> post("/api/v1/follows", %{"uri" => other_user.nickname}) - -    assert %{"id" => id} = json_response(conn, 200) -    assert id == to_string(other_user.id) -  end - -  test "following without reblogs" do -    follower = insert(:user) -    followed = insert(:user) -    other_user = insert(:user) - -    conn = -      build_conn() -      |> assign(:user, follower) -      |> post("/api/v1/accounts/#{followed.id}/follow?reblogs=false") - -    assert %{"showing_reblogs" => false} = json_response(conn, 200) - -    {:ok, activity} = CommonAPI.post(other_user, %{"status" => "hey"}) -    {:ok, reblog, _} = CommonAPI.repeat(activity.id, followed) - -    conn = -      build_conn() -      |> assign(:user, User.get_cached_by_id(follower.id)) -      |> get("/api/v1/timelines/home") - -    assert [] == json_response(conn, 200) - -    conn = -      build_conn() -      |> assign(:user, follower) -      |> post("/api/v1/accounts/#{followed.id}/follow?reblogs=true") - -    assert %{"showing_reblogs" => true} = json_response(conn, 200) - -    conn = -      build_conn() -      |> assign(:user, User.get_cached_by_id(follower.id)) -      |> get("/api/v1/timelines/home") - -    expected_activity_id = reblog.id -    assert [%{"id" => ^expected_activity_id}] = json_response(conn, 200) -  end - -  test "following / unfollowing errors" do -    user = insert(:user) - -    conn = -      build_conn() -      |> assign(:user, user) - -    # self follow -    conn_res = post(conn, "/api/v1/accounts/#{user.id}/follow") -    assert %{"error" => "Record not found"} = json_response(conn_res, 404) - -    # self unfollow -    user = User.get_cached_by_id(user.id) -    conn_res = post(conn, "/api/v1/accounts/#{user.id}/unfollow") -    assert %{"error" => "Record not found"} = json_response(conn_res, 404) - -    # self follow via uri -    user = User.get_cached_by_id(user.id) -    conn_res = post(conn, "/api/v1/follows", %{"uri" => user.nickname}) -    assert %{"error" => "Record not found"} = json_response(conn_res, 404) - -    # follow non existing user -    conn_res = post(conn, "/api/v1/accounts/doesntexist/follow") -    assert %{"error" => "Record not found"} = json_response(conn_res, 404) - -    # follow non existing user via uri -    conn_res = post(conn, "/api/v1/follows", %{"uri" => "doesntexist"}) -    assert %{"error" => "Record not found"} = json_response(conn_res, 404) - -    # unfollow non existing user -    conn_res = post(conn, "/api/v1/accounts/doesntexist/unfollow") -    assert %{"error" => "Record not found"} = json_response(conn_res, 404) -  end - -  describe "mute/unmute" do -    test "with notifications", %{conn: conn} do -      user = insert(:user) -      other_user = insert(:user) - -      conn = -        conn -        |> assign(:user, user) -        |> post("/api/v1/accounts/#{other_user.id}/mute") - -      response = json_response(conn, 200) - -      assert %{"id" => _id, "muting" => true, "muting_notifications" => true} = response -      user = User.get_cached_by_id(user.id) - -      conn = -        build_conn() -        |> assign(:user, user) -        |> post("/api/v1/accounts/#{other_user.id}/unmute") - -      response = json_response(conn, 200) -      assert %{"id" => _id, "muting" => false, "muting_notifications" => false} = response -    end - -    test "without notifications", %{conn: conn} do -      user = insert(:user) -      other_user = insert(:user) - -      conn = -        conn -        |> assign(:user, user) -        |> post("/api/v1/accounts/#{other_user.id}/mute", %{"notifications" => "false"}) - -      response = json_response(conn, 200) - -      assert %{"id" => _id, "muting" => true, "muting_notifications" => false} = response -      user = User.get_cached_by_id(user.id) - -      conn = -        build_conn() -        |> assign(:user, user) -        |> post("/api/v1/accounts/#{other_user.id}/unmute") - -      response = json_response(conn, 200) -      assert %{"id" => _id, "muting" => false, "muting_notifications" => false} = response -    end -  end - -  test "subscribing / unsubscribing to a user", %{conn: conn} do -    user = insert(:user) -    subscription_target = insert(:user) - -    conn = -      conn -      |> assign(:user, user) -      |> post("/api/v1/pleroma/accounts/#{subscription_target.id}/subscribe") - -    assert %{"id" => _id, "subscribing" => true} = json_response(conn, 200) - -    conn = -      build_conn() -      |> assign(:user, user) -      |> post("/api/v1/pleroma/accounts/#{subscription_target.id}/unsubscribe") - -    assert %{"id" => _id, "subscribing" => false} = json_response(conn, 200) -  end - -  test "getting a list of mutes", %{conn: conn} do -    user = insert(:user) -    other_user = insert(:user) - -    {:ok, user} = User.mute(user, other_user) - -    conn = -      conn -      |> assign(:user, user) -      |> get("/api/v1/mutes") - -    other_user_id = to_string(other_user.id) -    assert [%{"id" => ^other_user_id}] = json_response(conn, 200) -  end - -  test "blocking / unblocking a user", %{conn: conn} do -    user = insert(:user) -    other_user = insert(:user) - -    conn = -      conn -      |> assign(:user, user) -      |> post("/api/v1/accounts/#{other_user.id}/block") - -    assert %{"id" => _id, "blocking" => true} = json_response(conn, 200) - -    user = User.get_cached_by_id(user.id) - -    conn = -      build_conn() -      |> assign(:user, user) -      |> post("/api/v1/accounts/#{other_user.id}/unblock") - -    assert %{"id" => _id, "blocking" => false} = json_response(conn, 200) -  end - -  test "getting a list of blocks", %{conn: conn} do -    user = insert(:user) -    other_user = insert(:user) - -    {:ok, user} = User.block(user, other_user) - -    conn = -      conn -      |> assign(:user, user) -      |> get("/api/v1/blocks") - -    other_user_id = to_string(other_user.id) -    assert [%{"id" => ^other_user_id}] = json_response(conn, 200) -  end - -  test "blocking / unblocking a domain", %{conn: conn} do -    user = insert(:user) -    other_user = insert(:user, %{ap_id: "https://dogwhistle.zone/@pundit"}) - -    conn = -      conn -      |> assign(:user, user) -      |> post("/api/v1/domain_blocks", %{"domain" => "dogwhistle.zone"}) - -    assert %{} = json_response(conn, 200) -    user = User.get_cached_by_ap_id(user.ap_id) -    assert User.blocks?(user, other_user) - -    conn = -      build_conn() -      |> assign(:user, user) -      |> delete("/api/v1/domain_blocks", %{"domain" => "dogwhistle.zone"}) - -    assert %{} = json_response(conn, 200) -    user = User.get_cached_by_ap_id(user.ap_id) -    refute User.blocks?(user, other_user) -  end - -  test "getting a list of domain blocks", %{conn: conn} do -    user = insert(:user) - -    {:ok, user} = User.block_domain(user, "bad.site") -    {:ok, user} = User.block_domain(user, "even.worse.site") - -    conn = -      conn -      |> assign(:user, user) -      |> get("/api/v1/domain_blocks") - -    domain_blocks = json_response(conn, 200) - -    assert "bad.site" in domain_blocks -    assert "even.worse.site" in domain_blocks -  end - -  test "unimplemented follow_requests, blocks, domain blocks" do -    user = insert(:user) - -    ["blocks", "domain_blocks", "follow_requests"] -    |> Enum.each(fn endpoint -> -      conn = -        build_conn() -        |> assign(:user, user) -        |> get("/api/v1/#{endpoint}") - -      assert [] = json_response(conn, 200) -    end) -  end - -  test "returns the favorites of a user", %{conn: conn} do -    user = insert(:user) -    other_user = insert(:user) - -    {:ok, _} = CommonAPI.post(other_user, %{"status" => "bla"}) -    {:ok, activity} = CommonAPI.post(other_user, %{"status" => "traps are happy"}) - -    {:ok, _, _} = CommonAPI.favorite(activity.id, user) - -    first_conn = -      conn -      |> assign(:user, user) -      |> get("/api/v1/favourites") - -    assert [status] = json_response(first_conn, 200) -    assert status["id"] == to_string(activity.id) - -    assert [{"link", _link_header}] = -             Enum.filter(first_conn.resp_headers, fn element -> match?({"link", _}, element) end) - -    # Honours query params -    {:ok, second_activity} = -      CommonAPI.post(other_user, %{ -        "status" => -          "Trees Are Never Sad Look At Them Every Once In Awhile They're Quite Beautiful." -      }) - -    {:ok, _, _} = CommonAPI.favorite(second_activity.id, user) - -    last_like = status["id"] - -    second_conn = -      conn -      |> assign(:user, user) -      |> get("/api/v1/favourites?since_id=#{last_like}") - -    assert [second_status] = json_response(second_conn, 200) -    assert second_status["id"] == to_string(second_activity.id) - -    third_conn = -      conn -      |> assign(:user, user) -      |> get("/api/v1/favourites?limit=0") - -    assert [] = json_response(third_conn, 200) -  end - -  describe "getting favorites timeline of specified user" do -    setup do -      [current_user, user] = insert_pair(:user, %{info: %{hide_favorites: false}}) -      [current_user: current_user, user: user] -    end - -    test "returns list of statuses favorited by specified user", %{ -      conn: conn, -      current_user: current_user, -      user: user -    } do -      [activity | _] = insert_pair(:note_activity) -      CommonAPI.favorite(activity.id, user) - -      response = -        conn -        |> assign(:user, current_user) -        |> get("/api/v1/pleroma/accounts/#{user.id}/favourites") -        |> json_response(:ok) - -      [like] = response - -      assert length(response) == 1 -      assert like["id"] == activity.id -    end - -    test "returns favorites for specified user_id when user is not logged in", %{ -      conn: conn, -      user: user -    } do -      activity = insert(:note_activity) -      CommonAPI.favorite(activity.id, user) - -      response = -        conn -        |> get("/api/v1/pleroma/accounts/#{user.id}/favourites") -        |> json_response(:ok) - -      assert length(response) == 1 -    end - -    test "returns favorited DM only when user is logged in and he is one of recipients", %{ -      conn: conn, -      current_user: current_user, -      user: user -    } do -      {:ok, direct} = -        CommonAPI.post(current_user, %{ -          "status" => "Hi @#{user.nickname}!", -          "visibility" => "direct" -        }) - -      CommonAPI.favorite(direct.id, user) - -      response = -        conn -        |> assign(:user, current_user) -        |> get("/api/v1/pleroma/accounts/#{user.id}/favourites") -        |> json_response(:ok) - -      assert length(response) == 1 - -      anonymous_response = -        conn -        |> get("/api/v1/pleroma/accounts/#{user.id}/favourites") -        |> json_response(:ok) - -      assert Enum.empty?(anonymous_response) -    end - -    test "does not return others' favorited DM when user is not one of recipients", %{ -      conn: conn, -      current_user: current_user, -      user: user -    } do -      user_two = insert(:user) - -      {:ok, direct} = -        CommonAPI.post(user_two, %{ -          "status" => "Hi @#{user.nickname}!", -          "visibility" => "direct" -        }) - -      CommonAPI.favorite(direct.id, user) - -      response = -        conn -        |> assign(:user, current_user) -        |> get("/api/v1/pleroma/accounts/#{user.id}/favourites") -        |> json_response(:ok) - -      assert Enum.empty?(response) -    end - -    test "paginates favorites using since_id and max_id", %{ -      conn: conn, -      current_user: current_user, -      user: user -    } do -      activities = insert_list(10, :note_activity) - -      Enum.each(activities, fn activity -> -        CommonAPI.favorite(activity.id, user) -      end) - -      third_activity = Enum.at(activities, 2) -      seventh_activity = Enum.at(activities, 6) - -      response = -        conn -        |> assign(:user, current_user) -        |> get("/api/v1/pleroma/accounts/#{user.id}/favourites", %{ -          since_id: third_activity.id, -          max_id: seventh_activity.id -        }) -        |> json_response(:ok) - -      assert length(response) == 3 -      refute third_activity in response -      refute seventh_activity in response -    end - -    test "limits favorites using limit parameter", %{ -      conn: conn, -      current_user: current_user, -      user: user -    } do -      7 -      |> insert_list(:note_activity) -      |> Enum.each(fn activity -> -        CommonAPI.favorite(activity.id, user) -      end) - -      response = -        conn -        |> assign(:user, current_user) -        |> get("/api/v1/pleroma/accounts/#{user.id}/favourites", %{limit: "3"}) -        |> json_response(:ok) - -      assert length(response) == 3 -    end - -    test "returns empty response when user does not have any favorited statuses", %{ -      conn: conn, -      current_user: current_user, -      user: user -    } do -      response = -        conn -        |> assign(:user, current_user) -        |> get("/api/v1/pleroma/accounts/#{user.id}/favourites") -        |> json_response(:ok) - -      assert Enum.empty?(response) -    end - -    test "returns 404 error when specified user is not exist", %{conn: conn} do -      conn = get(conn, "/api/v1/pleroma/accounts/test/favourites") - -      assert json_response(conn, 404) == %{"error" => "Record not found"} -    end - -    test "returns 403 error when user has hidden own favorites", %{ -      conn: conn, -      current_user: current_user -    } do -      user = insert(:user, %{info: %{hide_favorites: true}}) -      activity = insert(:note_activity) -      CommonAPI.favorite(activity.id, user) - -      conn = -        conn -        |> assign(:user, current_user) -        |> get("/api/v1/pleroma/accounts/#{user.id}/favourites") - -      assert json_response(conn, 403) == %{"error" => "Can't get favorites"} -    end - -    test "hides favorites for new users by default", %{conn: conn, current_user: current_user} do -      user = insert(:user) -      activity = insert(:note_activity) -      CommonAPI.favorite(activity.id, user) - -      conn = -        conn -        |> assign(:user, current_user) -        |> get("/api/v1/pleroma/accounts/#{user.id}/favourites") - -      assert user.info.hide_favorites -      assert json_response(conn, 403) == %{"error" => "Can't get favorites"} -    end -  end - -  test "get instance information", %{conn: conn} do -    conn = get(conn, "/api/v1/instance") -    assert result = json_response(conn, 200) - -    email = Pleroma.Config.get([:instance, :email]) -    # Note: not checking for "max_toot_chars" since it's optional -    assert %{ -             "uri" => _, -             "title" => _, -             "description" => _, -             "version" => _, -             "email" => from_config_email, -             "urls" => %{ -               "streaming_api" => _ -             }, -             "stats" => _, -             "thumbnail" => _, -             "languages" => _, -             "registrations" => _, -             "poll_limits" => _ -           } = result - -    assert email == from_config_email -  end - -  test "get instance stats", %{conn: conn} do -    user = insert(:user, %{local: true}) - -    user2 = insert(:user, %{local: true}) -    {:ok, _user2} = User.deactivate(user2, !user2.info.deactivated) - -    insert(:user, %{local: false, nickname: "u@peer1.com"}) -    insert(:user, %{local: false, nickname: "u@peer2.com"}) - -    {:ok, _} = CommonAPI.post(user, %{"status" => "cofe"}) - -    # Stats should count users with missing or nil `info.deactivated` value -    user = User.get_cached_by_id(user.id) -    info_change = Changeset.change(user.info, %{deactivated: nil}) - -    {:ok, _user} = -      user -      |> Changeset.change() -      |> Changeset.put_embed(:info, info_change) -      |> User.update_and_set_cache() - -    Pleroma.Stats.update_stats() - -    conn = get(conn, "/api/v1/instance") - -    assert result = json_response(conn, 200) - -    stats = result["stats"] - -    assert stats -    assert stats["user_count"] == 1 -    assert stats["status_count"] == 1 -    assert stats["domain_count"] == 2 -  end - -  test "get peers", %{conn: conn} do -    insert(:user, %{local: false, nickname: "u@peer1.com"}) -    insert(:user, %{local: false, nickname: "u@peer2.com"}) - -    Pleroma.Stats.update_stats() - -    conn = get(conn, "/api/v1/instance/peers") - -    assert result = json_response(conn, 200) - -    assert ["peer1.com", "peer2.com"] == Enum.sort(result) -  end - -  test "put settings", %{conn: conn} do -    user = insert(:user) - -    conn = -      conn -      |> assign(:user, user) -      |> put("/api/web/settings", %{"data" => %{"programming" => "socks"}}) - -    assert _result = json_response(conn, 200) - -    user = User.get_cached_by_ap_id(user.ap_id) -    assert user.info.settings == %{"programming" => "socks"} -  end - -  describe "pinned statuses" do -    setup do -      Pleroma.Config.put([:instance, :max_pinned_statuses], 1) - -      user = insert(:user) -      {:ok, activity} = CommonAPI.post(user, %{"status" => "HI!!!"}) - -      [user: user, activity: activity] -    end - -    test "returns pinned statuses", %{conn: conn, user: user, activity: activity} do -      {:ok, _} = CommonAPI.pin(activity.id, user) - -      result = -        conn -        |> assign(:user, user) -        |> get("/api/v1/accounts/#{user.id}/statuses?pinned=true") +        |> get("/api/v1/endorsements")          |> json_response(200) -      id_str = to_string(activity.id) - -      assert [%{"id" => ^id_str, "pinned" => true}] = result -    end - -    test "pin status", %{conn: conn, user: user, activity: activity} do -      id_str = to_string(activity.id) - -      assert %{"id" => ^id_str, "pinned" => true} = -               conn -               |> assign(:user, user) -               |> post("/api/v1/statuses/#{activity.id}/pin") -               |> json_response(200) - -      assert [%{"id" => ^id_str, "pinned" => true}] = -               conn -               |> assign(:user, user) -               |> get("/api/v1/accounts/#{user.id}/statuses?pinned=true") -               |> json_response(200) -    end - -    test "/pin: returns 400 error when activity is not public", %{conn: conn, user: user} do -      {:ok, dm} = CommonAPI.post(user, %{"status" => "test", "visibility" => "direct"}) - -      conn = -        conn -        |> assign(:user, user) -        |> post("/api/v1/statuses/#{dm.id}/pin") - -      assert json_response(conn, 400) == %{"error" => "Could not pin"} +      assert res == []      end -    test "unpin status", %{conn: conn, user: user, activity: activity} do -      {:ok, _} = CommonAPI.pin(activity.id, user) - -      id_str = to_string(activity.id) -      user = refresh_record(user) - -      assert %{"id" => ^id_str, "pinned" => false} = -               conn -               |> assign(:user, user) -               |> post("/api/v1/statuses/#{activity.id}/unpin") -               |> json_response(200) - -      assert [] = -               conn -               |> assign(:user, user) -               |> get("/api/v1/accounts/#{user.id}/statuses?pinned=true") -               |> json_response(200) -    end - -    test "/unpin: returns 400 error when activity is not exist", %{conn: conn, user: user} do -      conn = -        conn -        |> assign(:user, user) -        |> post("/api/v1/statuses/1/unpin") - -      assert json_response(conn, 400) == %{"error" => "Could not unpin"} -    end - -    test "max pinned statuses", %{conn: conn, user: user, activity: activity_one} do -      {:ok, activity_two} = CommonAPI.post(user, %{"status" => "HI!!!"}) - -      id_str_one = to_string(activity_one.id) - -      assert %{"id" => ^id_str_one, "pinned" => true} = -               conn -               |> assign(:user, user) -               |> post("/api/v1/statuses/#{id_str_one}/pin") -               |> json_response(200) - -      user = refresh_record(user) - -      assert %{"error" => "You have already pinned the maximum number of statuses"} = -               conn -               |> assign(:user, user) -               |> post("/api/v1/statuses/#{activity_two.id}/pin") -               |> json_response(400) -    end -  end - -  describe "cards" do -    setup do -      Pleroma.Config.put([:rich_media, :enabled], true) - -      on_exit(fn -> -        Pleroma.Config.put([:rich_media, :enabled], false) -      end) - -      user = insert(:user) -      %{user: user} -    end - -    test "returns rich-media card", %{conn: conn, user: user} do -      {:ok, activity} = CommonAPI.post(user, %{"status" => "https://example.com/ogp"}) - -      card_data = %{ -        "image" => "http://ia.media-imdb.com/images/rock.jpg", -        "provider_name" => "www.imdb.com", -        "provider_url" => "http://www.imdb.com", -        "title" => "The Rock", -        "type" => "link", -        "url" => "http://www.imdb.com/title/tt0117500/", -        "description" => -          "Directed by Michael Bay. With Sean Connery, Nicolas Cage, Ed Harris, John Spencer.", -        "pleroma" => %{ -          "opengraph" => %{ -            "image" => "http://ia.media-imdb.com/images/rock.jpg", -            "title" => "The Rock", -            "type" => "video.movie", -            "url" => "http://www.imdb.com/title/tt0117500/", -            "description" => -              "Directed by Michael Bay. With Sean Connery, Nicolas Cage, Ed Harris, John Spencer." -          } -        } -      } - -      response = -        conn -        |> get("/api/v1/statuses/#{activity.id}/card") -        |> json_response(200) - -      assert response == card_data - -      # works with private posts -      {:ok, activity} = -        CommonAPI.post(user, %{"status" => "https://example.com/ogp", "visibility" => "direct"}) - -      response_two = +    test "GET /api/v1/trends", %{conn: conn} do +      res =          conn -        |> assign(:user, user) -        |> get("/api/v1/statuses/#{activity.id}/card") +        |> get("/api/v1/trends")          |> json_response(200) -      assert response_two == card_data -    end - -    test "replaces missing description with an empty string", %{conn: conn, user: user} do -      {:ok, activity} = -        CommonAPI.post(user, %{"status" => "https://example.com/ogp-missing-data"}) - -      response = -        conn -        |> get("/api/v1/statuses/#{activity.id}/card") -        |> json_response(:ok) - -      assert response == %{ -               "type" => "link", -               "title" => "Pleroma", -               "description" => "", -               "image" => nil, -               "provider_name" => "pleroma.social", -               "provider_url" => "https://pleroma.social", -               "url" => "https://pleroma.social/", -               "pleroma" => %{ -                 "opengraph" => %{ -                   "title" => "Pleroma", -                   "type" => "website", -                   "url" => "https://pleroma.social/" -                 } -               } -             } -    end -  end - -  test "bookmarks" do -    user = insert(:user) -    for_user = insert(:user) - -    {:ok, activity1} = -      CommonAPI.post(user, %{ -        "status" => "heweoo?" -      }) - -    {:ok, activity2} = -      CommonAPI.post(user, %{ -        "status" => "heweoo!" -      }) - -    response1 = -      build_conn() -      |> assign(:user, for_user) -      |> post("/api/v1/statuses/#{activity1.id}/bookmark") - -    assert json_response(response1, 200)["bookmarked"] == true - -    response2 = -      build_conn() -      |> assign(:user, for_user) -      |> post("/api/v1/statuses/#{activity2.id}/bookmark") - -    assert json_response(response2, 200)["bookmarked"] == true - -    bookmarks = -      build_conn() -      |> assign(:user, for_user) -      |> get("/api/v1/bookmarks") - -    assert [json_response(response2, 200), json_response(response1, 200)] == -             json_response(bookmarks, 200) - -    response1 = -      build_conn() -      |> assign(:user, for_user) -      |> post("/api/v1/statuses/#{activity1.id}/unbookmark") - -    assert json_response(response1, 200)["bookmarked"] == false - -    bookmarks = -      build_conn() -      |> assign(:user, for_user) -      |> get("/api/v1/bookmarks") - -    assert [json_response(response2, 200)] == json_response(bookmarks, 200) -  end - -  describe "conversation muting" do -    setup do -      user = insert(:user) -      {:ok, activity} = CommonAPI.post(user, %{"status" => "HIE"}) - -      [user: user, activity: activity] -    end - -    test "mute conversation", %{conn: conn, user: user, activity: activity} do -      id_str = to_string(activity.id) - -      assert %{"id" => ^id_str, "muted" => true} = -               conn -               |> assign(:user, user) -               |> post("/api/v1/statuses/#{activity.id}/mute") -               |> json_response(200) -    end - -    test "cannot mute already muted conversation", %{conn: conn, user: user, activity: activity} do -      {:ok, _} = CommonAPI.add_mute(user, activity) - -      conn = -        conn -        |> assign(:user, user) -        |> post("/api/v1/statuses/#{activity.id}/mute") - -      assert json_response(conn, 400) == %{"error" => "conversation is already muted"} -    end - -    test "unmute conversation", %{conn: conn, user: user, activity: activity} do -      {:ok, _} = CommonAPI.add_mute(user, activity) - -      id_str = to_string(activity.id) -      user = refresh_record(user) - -      assert %{"id" => ^id_str, "muted" => false} = -               conn -               |> assign(:user, user) -               |> post("/api/v1/statuses/#{activity.id}/unmute") -               |> json_response(200) -    end -  end - -  describe "reports" do -    setup do -      reporter = insert(:user) -      target_user = insert(:user) - -      {:ok, activity} = CommonAPI.post(target_user, %{"status" => "foobar"}) - -      [reporter: reporter, target_user: target_user, activity: activity] -    end - -    test "submit a basic report", %{conn: conn, reporter: reporter, target_user: target_user} do -      assert %{"action_taken" => false, "id" => _} = -               conn -               |> assign(:user, reporter) -               |> post("/api/v1/reports", %{"account_id" => target_user.id}) -               |> json_response(200) -    end - -    test "submit a report with statuses and comment", %{ -      conn: conn, -      reporter: reporter, -      target_user: target_user, -      activity: activity -    } do -      assert %{"action_taken" => false, "id" => _} = -               conn -               |> assign(:user, reporter) -               |> post("/api/v1/reports", %{ -                 "account_id" => target_user.id, -                 "status_ids" => [activity.id], -                 "comment" => "bad status!", -                 "forward" => "false" -               }) -               |> json_response(200) -    end - -    test "account_id is required", %{ -      conn: conn, -      reporter: reporter, -      activity: activity -    } do -      assert %{"error" => "Valid `account_id` required"} = -               conn -               |> assign(:user, reporter) -               |> post("/api/v1/reports", %{"status_ids" => [activity.id]}) -               |> json_response(400) -    end - -    test "comment must be up to the size specified in the config", %{ -      conn: conn, -      reporter: reporter, -      target_user: target_user -    } do -      max_size = Pleroma.Config.get([:instance, :max_report_comment_size], 1000) -      comment = String.pad_trailing("a", max_size + 1, "a") - -      error = %{"error" => "Comment must be up to #{max_size} characters"} - -      assert ^error = -               conn -               |> assign(:user, reporter) -               |> post("/api/v1/reports", %{"account_id" => target_user.id, "comment" => comment}) -               |> json_response(400) -    end - -    test "returns error when account is not exist", %{ -      conn: conn, -      reporter: reporter, -      activity: activity -    } do -      conn = -        conn -        |> assign(:user, reporter) -        |> post("/api/v1/reports", %{"status_ids" => [activity.id], "account_id" => "foo"}) - -      assert json_response(conn, 400) == %{"error" => "Account not found"} -    end -  end - -  describe "link headers" do -    test "preserves parameters in link headers", %{conn: conn} do -      user = insert(:user) -      other_user = insert(:user) - -      {:ok, activity1} = -        CommonAPI.post(other_user, %{ -          "status" => "hi @#{user.nickname}", -          "visibility" => "public" -        }) - -      {:ok, activity2} = -        CommonAPI.post(other_user, %{ -          "status" => "hi @#{user.nickname}", -          "visibility" => "public" -        }) - -      notification1 = Repo.get_by(Notification, activity_id: activity1.id) -      notification2 = Repo.get_by(Notification, activity_id: activity2.id) - -      conn = -        conn -        |> assign(:user, user) -        |> get("/api/v1/notifications", %{media_only: true}) - -      assert [link_header] = get_resp_header(conn, "link") -      assert link_header =~ ~r/media_only=true/ -      assert link_header =~ ~r/min_id=#{notification2.id}/ -      assert link_header =~ ~r/max_id=#{notification1.id}/ -    end -  end - -  test "accounts fetches correct account for nicknames beginning with numbers", %{conn: conn} do -    # Need to set an old-style integer ID to reproduce the problem -    # (these are no longer assigned to new accounts but were preserved -    # for existing accounts during the migration to flakeIDs) -    user_one = insert(:user, %{id: 1212}) -    user_two = insert(:user, %{nickname: "#{user_one.id}garbage"}) - -    resp_one = -      conn -      |> get("/api/v1/accounts/#{user_one.id}") - -    resp_two = -      conn -      |> get("/api/v1/accounts/#{user_two.nickname}") - -    resp_three = -      conn -      |> get("/api/v1/accounts/#{user_two.id}") - -    acc_one = json_response(resp_one, 200) -    acc_two = json_response(resp_two, 200) -    acc_three = json_response(resp_three, 200) -    refute acc_one == acc_two -    assert acc_two == acc_three -  end - -  describe "custom emoji" do -    test "with tags", %{conn: conn} do -      [emoji | _body] = -        conn -        |> get("/api/v1/custom_emojis") -        |> json_response(200) - -      assert Map.has_key?(emoji, "shortcode") -      assert Map.has_key?(emoji, "static_url") -      assert Map.has_key?(emoji, "tags") -      assert is_list(emoji["tags"]) -      assert Map.has_key?(emoji, "category") -      assert Map.has_key?(emoji, "url") -      assert Map.has_key?(emoji, "visible_in_picker") -    end -  end - -  describe "index/2 redirections" do -    setup %{conn: conn} do -      session_opts = [ -        store: :cookie, -        key: "_test", -        signing_salt: "cooldude" -      ] - -      conn = -        conn -        |> Plug.Session.call(Plug.Session.init(session_opts)) -        |> fetch_session() - -      test_path = "/web/statuses/test" -      %{conn: conn, path: test_path} -    end - -    test "redirects not logged-in users to the login page", %{conn: conn, path: path} do -      conn = get(conn, path) - -      assert conn.status == 302 -      assert redirected_to(conn) == "/web/login" -    end - -    test "does not redirect logged in users to the login page", %{conn: conn, path: path} do -      token = insert(:oauth_token) - -      conn = -        conn -        |> assign(:user, token.user) -        |> put_session(:oauth_token, token.token) -        |> get(path) - -      assert conn.status == 200 -    end - -    test "saves referer path to session", %{conn: conn, path: path} do -      conn = get(conn, path) -      return_to = Plug.Conn.get_session(conn, :return_to) - -      assert return_to == path -    end - -    test "redirects to the saved path after log in", %{conn: conn, path: path} do -      app = insert(:oauth_app, client_name: "Mastodon-Local", redirect_uris: ".") -      auth = insert(:oauth_authorization, app: app) - -      conn = -        conn -        |> put_session(:return_to, path) -        |> get("/web/login", %{code: auth.token}) - -      assert conn.status == 302 -      assert redirected_to(conn) == path -    end - -    test "redirects to the getting-started page when referer is not present", %{conn: conn} do -      app = insert(:oauth_app, client_name: "Mastodon-Local", redirect_uris: ".") -      auth = insert(:oauth_authorization, app: app) - -      conn = get(conn, "/web/login", %{code: auth.token}) - -      assert conn.status == 302 -      assert redirected_to(conn) == "/web/getting-started" -    end -  end - -  describe "scheduled activities" do -    test "creates a scheduled activity", %{conn: conn} do -      user = insert(:user) -      scheduled_at = NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(120), :millisecond) - -      conn = -        conn -        |> assign(:user, user) -        |> post("/api/v1/statuses", %{ -          "status" => "scheduled", -          "scheduled_at" => scheduled_at -        }) - -      assert %{"scheduled_at" => expected_scheduled_at} = json_response(conn, 200) -      assert expected_scheduled_at == Pleroma.Web.CommonAPI.Utils.to_masto_date(scheduled_at) -      assert [] == Repo.all(Activity) -    end - -    test "creates a scheduled activity with a media attachment", %{conn: conn} do -      user = insert(:user) -      scheduled_at = NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(120), :millisecond) - -      file = %Plug.Upload{ -        content_type: "image/jpg", -        path: Path.absname("test/fixtures/image.jpg"), -        filename: "an_image.jpg" -      } - -      {:ok, upload} = ActivityPub.upload(file, actor: user.ap_id) - -      conn = -        conn -        |> assign(:user, user) -        |> post("/api/v1/statuses", %{ -          "media_ids" => [to_string(upload.id)], -          "status" => "scheduled", -          "scheduled_at" => scheduled_at -        }) - -      assert %{"media_attachments" => [media_attachment]} = json_response(conn, 200) -      assert %{"type" => "image"} = media_attachment -    end - -    test "skips the scheduling and creates the activity if scheduled_at is earlier than 5 minutes from now", -         %{conn: conn} do -      user = insert(:user) - -      scheduled_at = -        NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(5) - 1, :millisecond) - -      conn = -        conn -        |> assign(:user, user) -        |> post("/api/v1/statuses", %{ -          "status" => "not scheduled", -          "scheduled_at" => scheduled_at -        }) - -      assert %{"content" => "not scheduled"} = json_response(conn, 200) -      assert [] == Repo.all(ScheduledActivity) -    end - -    test "returns error when daily user limit is exceeded", %{conn: conn} do -      user = insert(:user) - -      today = -        NaiveDateTime.utc_now() -        |> NaiveDateTime.add(:timer.minutes(6), :millisecond) -        |> NaiveDateTime.to_iso8601() - -      attrs = %{params: %{}, scheduled_at: today} -      {:ok, _} = ScheduledActivity.create(user, attrs) -      {:ok, _} = ScheduledActivity.create(user, attrs) - -      conn = -        conn -        |> assign(:user, user) -        |> post("/api/v1/statuses", %{"status" => "scheduled", "scheduled_at" => today}) - -      assert %{"error" => "daily limit exceeded"} == json_response(conn, 422) -    end - -    test "returns error when total user limit is exceeded", %{conn: conn} do -      user = insert(:user) - -      today = -        NaiveDateTime.utc_now() -        |> NaiveDateTime.add(:timer.minutes(6), :millisecond) -        |> NaiveDateTime.to_iso8601() - -      tomorrow = -        NaiveDateTime.utc_now() -        |> NaiveDateTime.add(:timer.hours(36), :millisecond) -        |> NaiveDateTime.to_iso8601() - -      attrs = %{params: %{}, scheduled_at: today} -      {:ok, _} = ScheduledActivity.create(user, attrs) -      {:ok, _} = ScheduledActivity.create(user, attrs) -      {:ok, _} = ScheduledActivity.create(user, %{params: %{}, scheduled_at: tomorrow}) - -      conn = -        conn -        |> assign(:user, user) -        |> post("/api/v1/statuses", %{"status" => "scheduled", "scheduled_at" => tomorrow}) - -      assert %{"error" => "total limit exceeded"} == json_response(conn, 422) -    end - -    test "shows scheduled activities", %{conn: conn} do -      user = insert(:user) -      scheduled_activity_id1 = insert(:scheduled_activity, user: user).id |> to_string() -      scheduled_activity_id2 = insert(:scheduled_activity, user: user).id |> to_string() -      scheduled_activity_id3 = insert(:scheduled_activity, user: user).id |> to_string() -      scheduled_activity_id4 = insert(:scheduled_activity, user: user).id |> to_string() - -      conn = -        conn -        |> assign(:user, user) - -      # min_id -      conn_res = -        conn -        |> get("/api/v1/scheduled_statuses?limit=2&min_id=#{scheduled_activity_id1}") - -      result = json_response(conn_res, 200) -      assert [%{"id" => ^scheduled_activity_id3}, %{"id" => ^scheduled_activity_id2}] = result - -      # since_id -      conn_res = -        conn -        |> get("/api/v1/scheduled_statuses?limit=2&since_id=#{scheduled_activity_id1}") - -      result = json_response(conn_res, 200) -      assert [%{"id" => ^scheduled_activity_id4}, %{"id" => ^scheduled_activity_id3}] = result - -      # max_id -      conn_res = -        conn -        |> get("/api/v1/scheduled_statuses?limit=2&max_id=#{scheduled_activity_id4}") - -      result = json_response(conn_res, 200) -      assert [%{"id" => ^scheduled_activity_id3}, %{"id" => ^scheduled_activity_id2}] = result -    end - -    test "shows a scheduled activity", %{conn: conn} do -      user = insert(:user) -      scheduled_activity = insert(:scheduled_activity, user: user) - -      res_conn = -        conn -        |> assign(:user, user) -        |> get("/api/v1/scheduled_statuses/#{scheduled_activity.id}") - -      assert %{"id" => scheduled_activity_id} = json_response(res_conn, 200) -      assert scheduled_activity_id == scheduled_activity.id |> to_string() - -      res_conn = -        conn -        |> assign(:user, user) -        |> get("/api/v1/scheduled_statuses/404") - -      assert %{"error" => "Record not found"} = json_response(res_conn, 404) -    end - -    test "updates a scheduled activity", %{conn: conn} do -      user = insert(:user) -      scheduled_activity = insert(:scheduled_activity, user: user) - -      new_scheduled_at = -        NaiveDateTime.add(NaiveDateTime.utc_now(), :timer.minutes(120), :millisecond) - -      res_conn = -        conn -        |> assign(:user, user) -        |> put("/api/v1/scheduled_statuses/#{scheduled_activity.id}", %{ -          scheduled_at: new_scheduled_at -        }) - -      assert %{"scheduled_at" => expected_scheduled_at} = json_response(res_conn, 200) -      assert expected_scheduled_at == Pleroma.Web.CommonAPI.Utils.to_masto_date(new_scheduled_at) - -      res_conn = -        conn -        |> assign(:user, user) -        |> put("/api/v1/scheduled_statuses/404", %{scheduled_at: new_scheduled_at}) - -      assert %{"error" => "Record not found"} = json_response(res_conn, 404) -    end - -    test "deletes a scheduled activity", %{conn: conn} do -      user = insert(:user) -      scheduled_activity = insert(:scheduled_activity, user: user) - -      res_conn = -        conn -        |> assign(:user, user) -        |> delete("/api/v1/scheduled_statuses/#{scheduled_activity.id}") - -      assert %{} = json_response(res_conn, 200) -      assert nil == Repo.get(ScheduledActivity, scheduled_activity.id) - -      res_conn = -        conn -        |> assign(:user, user) -        |> delete("/api/v1/scheduled_statuses/#{scheduled_activity.id}") - -      assert %{"error" => "Record not found"} = json_response(res_conn, 404) -    end -  end - -  test "Repeated posts that are replies incorrectly have in_reply_to_id null", %{conn: conn} do -    user1 = insert(:user) -    user2 = insert(:user) -    user3 = insert(:user) - -    {:ok, replied_to} = CommonAPI.post(user1, %{"status" => "cofe"}) - -    # Reply to status from another user -    conn1 = -      conn -      |> assign(:user, user2) -      |> post("/api/v1/statuses", %{"status" => "xD", "in_reply_to_id" => replied_to.id}) - -    assert %{"content" => "xD", "id" => id} = json_response(conn1, 200) - -    activity = Activity.get_by_id_with_object(id) - -    assert Object.normalize(activity).data["inReplyTo"] == Object.normalize(replied_to).data["id"] -    assert Activity.get_in_reply_to_activity(activity).id == replied_to.id - -    # Reblog from the third user -    conn2 = -      conn -      |> assign(:user, user3) -      |> post("/api/v1/statuses/#{activity.id}/reblog") - -    assert %{"reblog" => %{"id" => id, "reblogged" => true, "reblogs_count" => 1}} = -             json_response(conn2, 200) - -    assert to_string(activity.id) == id - -    # Getting third user status -    conn3 = -      conn -      |> assign(:user, user3) -      |> get("api/v1/timelines/home") - -    [reblogged_activity] = json_response(conn3, 200) - -    assert reblogged_activity["reblog"]["in_reply_to_id"] == replied_to.id - -    replied_to_user = User.get_by_ap_id(replied_to.data["actor"]) -    assert reblogged_activity["reblog"]["in_reply_to_account_id"] == replied_to_user.id -  end - -  describe "create account by app" do -    test "Account registration via Application", %{conn: conn} do -      conn = -        conn -        |> post("/api/v1/apps", %{ -          client_name: "client_name", -          redirect_uris: "urn:ietf:wg:oauth:2.0:oob", -          scopes: "read, write, follow" -        }) - -      %{ -        "client_id" => client_id, -        "client_secret" => client_secret, -        "id" => _, -        "name" => "client_name", -        "redirect_uri" => "urn:ietf:wg:oauth:2.0:oob", -        "vapid_key" => _, -        "website" => nil -      } = json_response(conn, 200) - -      conn = -        conn -        |> post("/oauth/token", %{ -          grant_type: "client_credentials", -          client_id: client_id, -          client_secret: client_secret -        }) - -      assert %{"access_token" => token, "refresh_token" => refresh, "scope" => scope} = -               json_response(conn, 200) - -      assert token -      token_from_db = Repo.get_by(Token, token: token) -      assert token_from_db -      assert refresh -      assert scope == "read write follow" - -      conn = -        build_conn() -        |> put_req_header("authorization", "Bearer " <> token) -        |> post("/api/v1/accounts", %{ -          username: "lain", -          email: "lain@example.org", -          password: "PlzDontHackLain", -          agreement: true -        }) - -      %{ -        "access_token" => token, -        "created_at" => _created_at, -        "scope" => _scope, -        "token_type" => "Bearer" -      } = json_response(conn, 200) - -      token_from_db = Repo.get_by(Token, token: token) -      assert token_from_db -      token_from_db = Repo.preload(token_from_db, :user) -      assert token_from_db.user - -      assert token_from_db.user.info.confirmation_pending -    end - -    test "rate limit", %{conn: conn} do -      app_token = insert(:oauth_token, user: nil) - -      conn = -        put_req_header(conn, "authorization", "Bearer " <> app_token.token) -        |> Map.put(:remote_ip, {15, 15, 15, 15}) - -      for i <- 1..5 do -        conn = -          conn -          |> post("/api/v1/accounts", %{ -            username: "#{i}lain", -            email: "#{i}lain@example.org", -            password: "PlzDontHackLain", -            agreement: true -          }) - -        %{ -          "access_token" => token, -          "created_at" => _created_at, -          "scope" => _scope, -          "token_type" => "Bearer" -        } = json_response(conn, 200) - -        token_from_db = Repo.get_by(Token, token: token) -        assert token_from_db -        token_from_db = Repo.preload(token_from_db, :user) -        assert token_from_db.user - -        assert token_from_db.user.info.confirmation_pending -      end - -      conn = -        conn -        |> post("/api/v1/accounts", %{ -          username: "6lain", -          email: "6lain@example.org", -          password: "PlzDontHackLain", -          agreement: true -        }) - -      assert json_response(conn, :too_many_requests) == %{"error" => "Throttled"} -    end -  end - -  describe "GET /api/v1/polls/:id" do -    test "returns poll entity for object id", %{conn: conn} do -      user = insert(:user) - -      {:ok, activity} = -        CommonAPI.post(user, %{ -          "status" => "Pleroma does", -          "poll" => %{"options" => ["what Mastodon't", "n't what Mastodoes"], "expires_in" => 20} -        }) - -      object = Object.normalize(activity) - -      conn = -        conn -        |> assign(:user, user) -        |> get("/api/v1/polls/#{object.id}") - -      response = json_response(conn, 200) -      id = to_string(object.id) -      assert %{"id" => ^id, "expired" => false, "multiple" => false} = response -    end - -    test "does not expose polls for private statuses", %{conn: conn} do -      user = insert(:user) -      other_user = insert(:user) - -      {:ok, activity} = -        CommonAPI.post(user, %{ -          "status" => "Pleroma does", -          "poll" => %{"options" => ["what Mastodon't", "n't what Mastodoes"], "expires_in" => 20}, -          "visibility" => "private" -        }) - -      object = Object.normalize(activity) - -      conn = -        conn -        |> assign(:user, other_user) -        |> get("/api/v1/polls/#{object.id}") - -      assert json_response(conn, 404) -    end -  end - -  describe "POST /api/v1/polls/:id/votes" do -    test "votes are added to the poll", %{conn: conn} do -      user = insert(:user) -      other_user = insert(:user) - -      {:ok, activity} = -        CommonAPI.post(user, %{ -          "status" => "A very delicious sandwich", -          "poll" => %{ -            "options" => ["Lettuce", "Grilled Bacon", "Tomato"], -            "expires_in" => 20, -            "multiple" => true -          } -        }) - -      object = Object.normalize(activity) - -      conn = -        conn -        |> assign(:user, other_user) -        |> post("/api/v1/polls/#{object.id}/votes", %{"choices" => [0, 1, 2]}) - -      assert json_response(conn, 200) -      object = Object.get_by_id(object.id) - -      assert Enum.all?(object.data["anyOf"], fn %{"replies" => %{"totalItems" => total_items}} -> -               total_items == 1 -             end) -    end - -    test "author can't vote", %{conn: conn} do -      user = insert(:user) - -      {:ok, activity} = -        CommonAPI.post(user, %{ -          "status" => "Am I cute?", -          "poll" => %{"options" => ["Yes", "No"], "expires_in" => 20} -        }) - -      object = Object.normalize(activity) - -      assert conn -             |> assign(:user, user) -             |> post("/api/v1/polls/#{object.id}/votes", %{"choices" => [1]}) -             |> json_response(422) == %{"error" => "Poll's author can't vote"} - -      object = Object.get_by_id(object.id) - -      refute Enum.at(object.data["oneOf"], 1)["replies"]["totalItems"] == 1 -    end - -    test "does not allow multiple choices on a single-choice question", %{conn: conn} do -      user = insert(:user) -      other_user = insert(:user) - -      {:ok, activity} = -        CommonAPI.post(user, %{ -          "status" => "The glass is", -          "poll" => %{"options" => ["half empty", "half full"], "expires_in" => 20} -        }) - -      object = Object.normalize(activity) - -      assert conn -             |> assign(:user, other_user) -             |> post("/api/v1/polls/#{object.id}/votes", %{"choices" => [0, 1]}) -             |> json_response(422) == %{"error" => "Too many choices"} - -      object = Object.get_by_id(object.id) - -      refute Enum.any?(object.data["oneOf"], fn %{"replies" => %{"totalItems" => total_items}} -> -               total_items == 1 -             end) -    end - -    test "does not allow choice index to be greater than options count", %{conn: conn} do -      user = insert(:user) -      other_user = insert(:user) - -      {:ok, activity} = -        CommonAPI.post(user, %{ -          "status" => "Am I cute?", -          "poll" => %{"options" => ["Yes", "No"], "expires_in" => 20} -        }) - -      object = Object.normalize(activity) - -      conn = -        conn -        |> assign(:user, other_user) -        |> post("/api/v1/polls/#{object.id}/votes", %{"choices" => [2]}) - -      assert json_response(conn, 422) == %{"error" => "Invalid indices"} -    end - -    test "returns 404 error when object is not exist", %{conn: conn} do -      user = insert(:user) - -      conn = -        conn -        |> assign(:user, user) -        |> post("/api/v1/polls/1/votes", %{"choices" => [0]}) - -      assert json_response(conn, 404) == %{"error" => "Record not found"} -    end - -    test "returns 404 when poll is private and not available for user", %{conn: conn} do -      user = insert(:user) -      other_user = insert(:user) - -      {:ok, activity} = -        CommonAPI.post(user, %{ -          "status" => "Am I cute?", -          "poll" => %{"options" => ["Yes", "No"], "expires_in" => 20}, -          "visibility" => "private" -        }) - -      object = Object.normalize(activity) - -      conn = -        conn -        |> assign(:user, other_user) -        |> post("/api/v1/polls/#{object.id}/votes", %{"choices" => [0]}) - -      assert json_response(conn, 404) == %{"error" => "Record not found"} -    end -  end - -  describe "GET /api/v1/statuses/:id/favourited_by" do -    setup do -      user = insert(:user) -      {:ok, activity} = CommonAPI.post(user, %{"status" => "test"}) - -      conn = -        build_conn() -        |> assign(:user, user) - -      [conn: conn, activity: activity] -    end - -    test "returns users who have favorited the status", %{conn: conn, activity: activity} do -      other_user = insert(:user) -      {:ok, _, _} = CommonAPI.favorite(activity.id, other_user) - -      response = -        conn -        |> get("/api/v1/statuses/#{activity.id}/favourited_by") -        |> json_response(:ok) - -      [%{"id" => id}] = response - -      assert id == other_user.id -    end - -    test "returns empty array when status has not been favorited yet", %{ -      conn: conn, -      activity: activity -    } do -      response = -        conn -        |> get("/api/v1/statuses/#{activity.id}/favourited_by") -        |> json_response(:ok) - -      assert Enum.empty?(response) -    end -  end - -  describe "GET /api/v1/statuses/:id/reblogged_by" do -    setup do -      user = insert(:user) -      {:ok, activity} = CommonAPI.post(user, %{"status" => "test"}) - -      conn = -        build_conn() -        |> assign(:user, user) - -      [conn: conn, activity: activity] -    end - -    test "returns users who have reblogged the status", %{conn: conn, activity: activity} do -      other_user = insert(:user) -      {:ok, _, _} = CommonAPI.repeat(activity.id, other_user) - -      response = -        conn -        |> get("/api/v1/statuses/#{activity.id}/reblogged_by") -        |> json_response(:ok) - -      [%{"id" => id}] = response - -      assert id == other_user.id -    end - -    test "returns empty array when status has not been reblogged yet", %{ -      conn: conn, -      activity: activity -    } do -      response = -        conn -        |> get("/api/v1/statuses/#{activity.id}/reblogged_by") -        |> json_response(:ok) - -      assert Enum.empty?(response) -    end -  end - -  describe "POST /auth/password, with valid parameters" do -    setup %{conn: conn} do -      user = insert(:user) -      conn = post(conn, "/auth/password?email=#{user.email}") -      %{conn: conn, user: user} -    end - -    test "it returns 204", %{conn: conn} do -      assert json_response(conn, :no_content) -    end - -    test "it creates a PasswordResetToken record for user", %{user: user} do -      token_record = Repo.get_by(Pleroma.PasswordResetToken, user_id: user.id) -      assert token_record -    end - -    test "it sends an email to user", %{user: user} do -      token_record = Repo.get_by(Pleroma.PasswordResetToken, user_id: user.id) - -      email = Pleroma.Emails.UserEmail.password_reset_email(user, token_record.token) -      notify_email = Pleroma.Config.get([:instance, :notify_email]) -      instance_name = Pleroma.Config.get([:instance, :name]) - -      assert_email_sent( -        from: {instance_name, notify_email}, -        to: {user.name, user.email}, -        html_body: email.html_body -      ) -    end -  end - -  describe "POST /auth/password, with invalid parameters" do -    setup do -      user = insert(:user) -      {:ok, user: user} -    end - -    test "it returns 404 when user is not found", %{conn: conn, user: user} do -      conn = post(conn, "/auth/password?email=nonexisting_#{user.email}") -      assert conn.status == 404 -      assert conn.resp_body == "" -    end - -    test "it returns 400 when user is not local", %{conn: conn, user: user} do -      {:ok, user} = Repo.update(Changeset.change(user, local: false)) -      conn = post(conn, "/auth/password?email=#{user.email}") -      assert conn.status == 400 -      assert conn.resp_body == "" +      assert res == []      end    end  end diff --git a/test/web/mastodon_api/mastodon_api_test.exs b/test/web/mastodon_api/mastodon_api_test.exs new file mode 100644 index 000000000..561ef05aa --- /dev/null +++ b/test/web/mastodon_api/mastodon_api_test.exs @@ -0,0 +1,104 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.MastodonAPI.MastodonAPITest do +  use Pleroma.Web.ConnCase + +  alias Pleroma.Notification +  alias Pleroma.ScheduledActivity +  alias Pleroma.User +  alias Pleroma.Web.CommonAPI +  alias Pleroma.Web.MastodonAPI.MastodonAPI + +  import Pleroma.Factory + +  describe "follow/3" do +    test "returns error when followed user is deactivated" do +      follower = insert(:user) +      user = insert(:user, local: true, deactivated: true) +      {:error, error} = MastodonAPI.follow(follower, user) +      assert error == "Could not follow user: #{user.nickname} is deactivated." +    end + +    test "following for user" do +      follower = insert(:user) +      user = insert(:user) +      {:ok, follower} = MastodonAPI.follow(follower, user) +      assert User.following?(follower, user) +    end + +    test "returns ok if user already followed" do +      follower = insert(:user) +      user = insert(:user) +      {:ok, follower} = User.follow(follower, user) +      {:ok, follower} = MastodonAPI.follow(follower, refresh_record(user)) +      assert User.following?(follower, user) +    end +  end + +  describe "get_followers/2" do +    test "returns user followers" do +      follower1_user = insert(:user) +      follower2_user = insert(:user) +      user = insert(:user) +      {:ok, _follower1_user} = User.follow(follower1_user, user) +      {:ok, follower2_user} = User.follow(follower2_user, user) + +      assert MastodonAPI.get_followers(user, %{"limit" => 1}) == [follower2_user] +    end +  end + +  describe "get_friends/2" do +    test "returns user friends" do +      user = insert(:user) +      followed_one = insert(:user) +      followed_two = insert(:user) +      followed_three = insert(:user) + +      {:ok, user} = User.follow(user, followed_one) +      {:ok, user} = User.follow(user, followed_two) +      {:ok, user} = User.follow(user, followed_three) +      res = MastodonAPI.get_friends(user) + +      assert length(res) == 3 +      assert Enum.member?(res, refresh_record(followed_three)) +      assert Enum.member?(res, refresh_record(followed_two)) +      assert Enum.member?(res, refresh_record(followed_one)) +    end +  end + +  describe "get_notifications/2" do +    test "returns notifications for user" do +      user = insert(:user) +      subscriber = insert(:user) + +      User.subscribe(subscriber, user) + +      {:ok, status} = CommonAPI.post(user, %{"status" => "Akariiiin"}) + +      {:ok, status1} = CommonAPI.post(user, %{"status" => "Magi"}) +      {:ok, [notification]} = Notification.create_notifications(status) +      {:ok, [notification1]} = Notification.create_notifications(status1) +      res = MastodonAPI.get_notifications(subscriber) + +      assert Enum.member?(Enum.map(res, & &1.id), notification.id) +      assert Enum.member?(Enum.map(res, & &1.id), notification1.id) +    end +  end + +  describe "get_scheduled_activities/2" do +    test "returns user scheduled activities" do +      user = insert(:user) + +      today = +        NaiveDateTime.utc_now() +        |> NaiveDateTime.add(:timer.minutes(6), :millisecond) +        |> NaiveDateTime.to_iso8601() + +      attrs = %{params: %{}, scheduled_at: today} +      {:ok, schedule} = ScheduledActivity.create(user, attrs) +      assert MastodonAPI.get_scheduled_activities(user) == [schedule] +    end +  end +end diff --git a/test/web/mastodon_api/account_view_test.exs b/test/web/mastodon_api/views/account_view_test.exs index fa44d35cc..2107bb85c 100644 --- a/test/web/mastodon_api/account_view_test.exs +++ b/test/web/mastodon_api/views/account_view_test.exs @@ -1,5 +1,5 @@  # Pleroma: A lightweight social networking server -# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.Web.MastodonAPI.AccountViewTest do @@ -26,12 +26,10 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do      user =        insert(:user, %{ -        info: %{ -          note_count: 5, -          follower_count: 3, -          source_data: source_data, -          background: background_image -        }, +        follower_count: 3, +        note_count: 5, +        source_data: source_data, +        background: background_image,          nickname: "shp@shitposter.club",          name: ":karjalanpiirakka: shp",          bio: "<script src=\"invalid-html\"></script><span>valid html</span>", @@ -67,7 +65,11 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do        source: %{          note: "valid html",          sensitive: false, -        pleroma: %{} +        pleroma: %{ +          actor_type: "Person", +          discoverable: false +        }, +        fields: []        },        pleroma: %{          background_image: "https://example.com/images/asuka_hospital.png", @@ -78,36 +80,35 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do          hide_favorites: true,          hide_followers: false,          hide_follows: false, +        hide_followers_count: false, +        hide_follows_count: false,          relationship: %{},          skip_thread_containment: false        }      } -    assert expected == AccountView.render("account.json", %{user: user}) +    assert expected == AccountView.render("show.json", %{user: user})    end    test "Represent the user account for the account owner" do      user = insert(:user) -    notification_settings = %{ -      "followers" => true, -      "follows" => true, -      "non_follows" => true, -      "non_followers" => true -    } - -    privacy = user.info.default_scope +    notification_settings = %Pleroma.User.NotificationSetting{} +    privacy = user.default_scope      assert %{ -             pleroma: %{notification_settings: ^notification_settings}, +             pleroma: %{notification_settings: ^notification_settings, allow_following_move: true},               source: %{privacy: ^privacy} -           } = AccountView.render("account.json", %{user: user, for: user}) +           } = AccountView.render("show.json", %{user: user, for: user})    end    test "Represent a Service(bot) account" do      user =        insert(:user, %{ -        info: %{note_count: 5, follower_count: 3, source_data: %{"type" => "Service"}}, +        follower_count: 3, +        note_count: 5, +        source_data: %{}, +        actor_type: "Service",          nickname: "shp@shitposter.club",          inserted_at: ~N[2017-08-15 15:47:06.597036]        }) @@ -134,7 +135,11 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do        source: %{          note: user.bio,          sensitive: false, -        pleroma: %{} +        pleroma: %{ +          actor_type: "Service", +          discoverable: false +        }, +        fields: []        },        pleroma: %{          background_image: nil, @@ -145,18 +150,20 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do          hide_favorites: true,          hide_followers: false,          hide_follows: false, +        hide_followers_count: false, +        hide_follows_count: false,          relationship: %{},          skip_thread_containment: false        }      } -    assert expected == AccountView.render("account.json", %{user: user}) +    assert expected == AccountView.render("show.json", %{user: user})    end    test "Represent a deactivated user for an admin" do -    admin = insert(:user, %{info: %{is_admin: true}}) -    deactivated_user = insert(:user, %{info: %{deactivated: true}}) -    represented = AccountView.render("account.json", %{user: deactivated_user, for: admin}) +    admin = insert(:user, is_admin: true) +    deactivated_user = insert(:user, deactivated: true) +    represented = AccountView.render("show.json", %{user: deactivated_user, for: admin})      assert represented[:pleroma][:deactivated] == true    end @@ -180,9 +187,9 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do        {:ok, user} = User.follow(user, other_user)        {:ok, other_user} = User.follow(other_user, user) -      {:ok, other_user} = User.subscribe(user, other_user) -      {:ok, user} = User.mute(user, other_user, true) -      {:ok, user} = CommonAPI.hide_reblogs(user, other_user) +      {:ok, _subscription} = User.subscribe(user, other_user) +      {:ok, _user_relationships} = User.mute(user, other_user, true) +      {:ok, _reblog_mute} = CommonAPI.hide_reblogs(user, other_user)        expected = %{          id: to_string(other_user.id), @@ -208,9 +215,9 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do        other_user = insert(:user)        {:ok, user} = User.follow(user, other_user) -      {:ok, other_user} = User.subscribe(user, other_user) -      {:ok, user} = User.block(user, other_user) -      {:ok, other_user} = User.block(other_user, user) +      {:ok, _subscription} = User.subscribe(user, other_user) +      {:ok, _user_relationship} = User.block(user, other_user) +      {:ok, _user_relationship} = User.block(other_user, user)        expected = %{          id: to_string(other_user.id), @@ -231,9 +238,19 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do                 AccountView.render("relationship.json", %{user: user, target: other_user})      end +    test "represent a relationship for the user blocking a domain" do +      user = insert(:user) +      other_user = insert(:user, ap_id: "https://bad.site/users/other_user") + +      {:ok, user} = User.block_domain(user, "bad.site") + +      assert %{domain_blocking: true, blocking: false} = +               AccountView.render("relationship.json", %{user: user, target: other_user}) +    end +      test "represent a relationship for the user with a pending follow request" do        user = insert(:user) -      other_user = insert(:user, %{info: %User.Info{locked: true}}) +      other_user = insert(:user, locked: true)        {:ok, user, other_user, _} = CommonAPI.follow(user, other_user)        user = User.get_cached_by_id(user.id) @@ -262,14 +279,17 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do    test "represent an embedded relationship" do      user =        insert(:user, %{ -        info: %{note_count: 5, follower_count: 0, source_data: %{"type" => "Service"}}, +        follower_count: 0, +        note_count: 5, +        source_data: %{}, +        actor_type: "Service",          nickname: "shp@shitposter.club",          inserted_at: ~N[2017-08-15 15:47:06.597036]        })      other_user = insert(:user)      {:ok, other_user} = User.follow(other_user, user) -    {:ok, other_user} = User.block(other_user, user) +    {:ok, _user_relationship} = User.block(other_user, user)      {:ok, _} = User.follow(insert(:user), user)      expected = %{ @@ -294,7 +314,11 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do        source: %{          note: user.bio,          sensitive: false, -        pleroma: %{} +        pleroma: %{ +          actor_type: "Service", +          discoverable: false +        }, +        fields: []        },        pleroma: %{          background_image: nil, @@ -305,6 +329,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do          hide_favorites: true,          hide_followers: false,          hide_follows: false, +        hide_followers_count: false, +        hide_follows_count: false,          relationship: %{            id: to_string(user.id),            following: false, @@ -323,27 +349,179 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do        }      } -    assert expected == AccountView.render("account.json", %{user: user, for: other_user}) +    assert expected == +             AccountView.render("show.json", %{user: refresh_record(user), for: other_user})    end    test "returns the settings store if the requesting user is the represented user and it's requested specifically" do -    user = insert(:user, %{info: %User.Info{pleroma_settings_store: %{fe: "test"}}}) +    user = insert(:user, pleroma_settings_store: %{fe: "test"})      result = -      AccountView.render("account.json", %{user: user, for: user, with_pleroma_settings: true}) +      AccountView.render("show.json", %{user: user, for: user, with_pleroma_settings: true})      assert result.pleroma.settings_store == %{:fe => "test"} -    result = AccountView.render("account.json", %{user: user, with_pleroma_settings: true}) +    result = AccountView.render("show.json", %{user: user, with_pleroma_settings: true})      assert result.pleroma[:settings_store] == nil -    result = AccountView.render("account.json", %{user: user, for: user}) +    result = AccountView.render("show.json", %{user: user, for: user})      assert result.pleroma[:settings_store] == nil    end    test "sanitizes display names" do      user = insert(:user, name: "<marquee> username </marquee>") -    result = AccountView.render("account.json", %{user: user}) +    result = AccountView.render("show.json", %{user: user})      refute result.display_name == "<marquee> username </marquee>"    end + +  test "never display nil user follow counts" do +    user = insert(:user, following_count: 0, follower_count: 0) +    result = AccountView.render("show.json", %{user: user}) + +    assert result.following_count == 0 +    assert result.followers_count == 0 +  end + +  describe "hiding follows/following" do +    test "shows when follows/followers stats are hidden and sets follow/follower count to 0" do +      user = +        insert(:user, %{ +          hide_followers: true, +          hide_followers_count: true, +          hide_follows: true, +          hide_follows_count: true +        }) + +      other_user = insert(:user) +      {:ok, user, other_user, _activity} = CommonAPI.follow(user, other_user) +      {:ok, _other_user, user, _activity} = CommonAPI.follow(other_user, user) + +      assert %{ +               followers_count: 0, +               following_count: 0, +               pleroma: %{hide_follows_count: true, hide_followers_count: true} +             } = AccountView.render("show.json", %{user: user}) +    end + +    test "shows when follows/followers are hidden" do +      user = insert(:user, hide_followers: true, hide_follows: true) +      other_user = insert(:user) +      {:ok, user, other_user, _activity} = CommonAPI.follow(user, other_user) +      {:ok, _other_user, user, _activity} = CommonAPI.follow(other_user, user) + +      assert %{ +               followers_count: 1, +               following_count: 1, +               pleroma: %{hide_follows: true, hide_followers: true} +             } = AccountView.render("show.json", %{user: user}) +    end + +    test "shows actual follower/following count to the account owner" do +      user = insert(:user, hide_followers: true, hide_follows: true) +      other_user = insert(:user) +      {:ok, user, other_user, _activity} = CommonAPI.follow(user, other_user) +      {:ok, _other_user, user, _activity} = CommonAPI.follow(other_user, user) + +      assert %{ +               followers_count: 1, +               following_count: 1 +             } = AccountView.render("show.json", %{user: user, for: user}) +    end + +    test "shows unread_conversation_count only to the account owner" do +      user = insert(:user) +      other_user = insert(:user) + +      {:ok, _activity} = +        CommonAPI.post(other_user, %{ +          "status" => "Hey @#{user.nickname}.", +          "visibility" => "direct" +        }) + +      user = User.get_cached_by_ap_id(user.ap_id) + +      assert AccountView.render("show.json", %{user: user, for: other_user})[:pleroma][ +               :unread_conversation_count +             ] == nil + +      assert AccountView.render("show.json", %{user: user, for: user})[:pleroma][ +               :unread_conversation_count +             ] == 1 +    end +  end + +  describe "follow requests counter" do +    test "shows zero when no follow requests are pending" do +      user = insert(:user) + +      assert %{follow_requests_count: 0} = +               AccountView.render("show.json", %{user: user, for: user}) + +      other_user = insert(:user) +      {:ok, _other_user, user, _activity} = CommonAPI.follow(other_user, user) + +      assert %{follow_requests_count: 0} = +               AccountView.render("show.json", %{user: user, for: user}) +    end + +    test "shows non-zero when follow requests are pending" do +      user = insert(:user, locked: true) + +      assert %{locked: true} = AccountView.render("show.json", %{user: user, for: user}) + +      other_user = insert(:user) +      {:ok, _other_user, user, _activity} = CommonAPI.follow(other_user, user) + +      assert %{locked: true, follow_requests_count: 1} = +               AccountView.render("show.json", %{user: user, for: user}) +    end + +    test "decreases when accepting a follow request" do +      user = insert(:user, locked: true) + +      assert %{locked: true} = AccountView.render("show.json", %{user: user, for: user}) + +      other_user = insert(:user) +      {:ok, other_user, user, _activity} = CommonAPI.follow(other_user, user) + +      assert %{locked: true, follow_requests_count: 1} = +               AccountView.render("show.json", %{user: user, for: user}) + +      {:ok, _other_user} = CommonAPI.accept_follow_request(other_user, user) + +      assert %{locked: true, follow_requests_count: 0} = +               AccountView.render("show.json", %{user: user, for: user}) +    end + +    test "decreases when rejecting a follow request" do +      user = insert(:user, locked: true) + +      assert %{locked: true} = AccountView.render("show.json", %{user: user, for: user}) + +      other_user = insert(:user) +      {:ok, other_user, user, _activity} = CommonAPI.follow(other_user, user) + +      assert %{locked: true, follow_requests_count: 1} = +               AccountView.render("show.json", %{user: user, for: user}) + +      {:ok, _other_user} = CommonAPI.reject_follow_request(other_user, user) + +      assert %{locked: true, follow_requests_count: 0} = +               AccountView.render("show.json", %{user: user, for: user}) +    end + +    test "shows non-zero when historical unapproved requests are present" do +      user = insert(:user, locked: true) + +      assert %{locked: true} = AccountView.render("show.json", %{user: user, for: user}) + +      other_user = insert(:user) +      {:ok, _other_user, user, _activity} = CommonAPI.follow(other_user, user) + +      {:ok, user} = User.update_and_set_cache(user, %{locked: false}) + +      assert %{locked: false, follow_requests_count: 1} = +               AccountView.render("show.json", %{user: user, for: user}) +    end +  end  end diff --git a/test/web/mastodon_api/views/conversation_view_test.exs b/test/web/mastodon_api/views/conversation_view_test.exs new file mode 100644 index 000000000..6ed22597d --- /dev/null +++ b/test/web/mastodon_api/views/conversation_view_test.exs @@ -0,0 +1,35 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.MastodonAPI.ConversationViewTest do +  use Pleroma.DataCase + +  alias Pleroma.Conversation.Participation +  alias Pleroma.Web.CommonAPI +  alias Pleroma.Web.MastodonAPI.ConversationView + +  import Pleroma.Factory + +  test "represents a Mastodon Conversation entity" do +    user = insert(:user) +    other_user = insert(:user) + +    {:ok, activity} = +      CommonAPI.post(user, %{"status" => "hey @#{other_user.nickname}", "visibility" => "direct"}) + +    [participation] = Participation.for_user_with_last_activity_id(user) + +    assert participation + +    conversation = +      ConversationView.render("participation.json", %{participation: participation, for: user}) + +    assert conversation.id == participation.id |> to_string() +    assert conversation.last_status.id == activity.id + +    assert [account] = conversation.accounts +    assert account.id == other_user.id +    assert conversation.last_status.pleroma.direct_conversation_id == participation.id +  end +end diff --git a/test/web/mastodon_api/views/list_view_test.exs b/test/web/mastodon_api/views/list_view_test.exs new file mode 100644 index 000000000..59e896a7c --- /dev/null +++ b/test/web/mastodon_api/views/list_view_test.exs @@ -0,0 +1,32 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.MastodonAPI.ListViewTest do +  use Pleroma.DataCase +  import Pleroma.Factory +  alias Pleroma.Web.MastodonAPI.ListView + +  test "show" do +    user = insert(:user) +    title = "mortal enemies" +    {:ok, list} = Pleroma.List.create(title, user) + +    expected = %{ +      id: to_string(list.id), +      title: title +    } + +    assert expected == ListView.render("show.json", %{list: list}) +  end + +  test "index" do +    user = insert(:user) + +    {:ok, list} = Pleroma.List.create("my list", user) +    {:ok, list2} = Pleroma.List.create("cofe", user) + +    assert [%{id: _, title: "my list"}, %{id: _, title: "cofe"}] = +             ListView.render("index.json", lists: [list, list2]) +  end +end diff --git a/test/web/mastodon_api/views/marker_view_test.exs b/test/web/mastodon_api/views/marker_view_test.exs new file mode 100644 index 000000000..8a5c89d56 --- /dev/null +++ b/test/web/mastodon_api/views/marker_view_test.exs @@ -0,0 +1,27 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.MastodonAPI.MarkerViewTest do +  use Pleroma.DataCase +  alias Pleroma.Web.MastodonAPI.MarkerView +  import Pleroma.Factory + +  test "returns markers" do +    marker1 = insert(:marker, timeline: "notifications", last_read_id: "17") +    marker2 = insert(:marker, timeline: "home", last_read_id: "42") + +    assert MarkerView.render("markers.json", %{markers: [marker1, marker2]}) == %{ +             "home" => %{ +               last_read_id: "42", +               updated_at: NaiveDateTime.to_iso8601(marker2.updated_at), +               version: 0 +             }, +             "notifications" => %{ +               last_read_id: "17", +               updated_at: NaiveDateTime.to_iso8601(marker1.updated_at), +               version: 0 +             } +           } +  end +end diff --git a/test/web/mastodon_api/notification_view_test.exs b/test/web/mastodon_api/views/notification_view_test.exs index 977ea1e87..1fe83cb2c 100644 --- a/test/web/mastodon_api/notification_view_test.exs +++ b/test/web/mastodon_api/views/notification_view_test.exs @@ -1,5 +1,5 @@  # Pleroma: A lightweight social networking server -# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.Web.MastodonAPI.NotificationViewTest do @@ -27,8 +27,8 @@ defmodule Pleroma.Web.MastodonAPI.NotificationViewTest do        id: to_string(notification.id),        pleroma: %{is_seen: false},        type: "mention", -      account: AccountView.render("account.json", %{user: user, for: mentioned_user}), -      status: StatusView.render("status.json", %{activity: activity, for: mentioned_user}), +      account: AccountView.render("show.json", %{user: user, for: mentioned_user}), +      status: StatusView.render("show.json", %{activity: activity, for: mentioned_user}),        created_at: Utils.to_masto_date(notification.inserted_at)      } @@ -50,8 +50,8 @@ defmodule Pleroma.Web.MastodonAPI.NotificationViewTest do        id: to_string(notification.id),        pleroma: %{is_seen: false},        type: "favourite", -      account: AccountView.render("account.json", %{user: another_user, for: user}), -      status: StatusView.render("status.json", %{activity: create_activity, for: user}), +      account: AccountView.render("show.json", %{user: another_user, for: user}), +      status: StatusView.render("show.json", %{activity: create_activity, for: user}),        created_at: Utils.to_masto_date(notification.inserted_at)      } @@ -72,8 +72,8 @@ defmodule Pleroma.Web.MastodonAPI.NotificationViewTest do        id: to_string(notification.id),        pleroma: %{is_seen: false},        type: "reblog", -      account: AccountView.render("account.json", %{user: another_user, for: user}), -      status: StatusView.render("status.json", %{activity: reblog_activity, for: user}), +      account: AccountView.render("show.json", %{user: another_user, for: user}), +      status: StatusView.render("show.json", %{activity: reblog_activity, for: user}),        created_at: Utils.to_masto_date(notification.inserted_at)      } @@ -92,7 +92,7 @@ defmodule Pleroma.Web.MastodonAPI.NotificationViewTest do        id: to_string(notification.id),        pleroma: %{is_seen: false},        type: "follow", -      account: AccountView.render("account.json", %{user: follower, for: followed}), +      account: AccountView.render("show.json", %{user: follower, for: followed}),        created_at: Utils.to_masto_date(notification.inserted_at)      } @@ -100,5 +100,65 @@ defmodule Pleroma.Web.MastodonAPI.NotificationViewTest do        NotificationView.render("index.json", %{notifications: [notification], for: followed})      assert [expected] == result + +    User.perform(:delete, follower) +    notification = Notification |> Repo.one() |> Repo.preload(:activity) + +    assert [] == +             NotificationView.render("index.json", %{notifications: [notification], for: followed}) +  end + +  test "Move notification" do +    old_user = insert(:user) +    new_user = insert(:user, also_known_as: [old_user.ap_id]) +    follower = insert(:user) + +    User.follow(follower, old_user) +    Pleroma.Web.ActivityPub.ActivityPub.move(old_user, new_user) +    Pleroma.Tests.ObanHelpers.perform_all() + +    old_user = refresh_record(old_user) +    new_user = refresh_record(new_user) + +    [notification] = Notification.for_user(follower, %{with_move: true}) + +    expected = %{ +      id: to_string(notification.id), +      pleroma: %{is_seen: false}, +      type: "move", +      account: AccountView.render("show.json", %{user: old_user, for: follower}), +      target: AccountView.render("show.json", %{user: new_user, for: follower}), +      created_at: Utils.to_masto_date(notification.inserted_at) +    } + +    assert [expected] == +             NotificationView.render("index.json", %{notifications: [notification], for: follower}) +  end + +  test "EmojiReaction notification" do +    user = insert(:user) +    other_user = insert(:user) + +    {:ok, activity} = CommonAPI.post(user, %{"status" => "#cofe"}) +    {:ok, _activity, _} = CommonAPI.react_with_emoji(activity.id, other_user, "☕") + +    activity = Repo.get(Activity, activity.id) + +    [notification] = Notification.for_user(user) + +    assert notification + +    expected = %{ +      id: to_string(notification.id), +      pleroma: %{is_seen: false}, +      type: "pleroma:emoji_reaction", +      emoji: "☕", +      account: AccountView.render("show.json", %{user: other_user, for: user}), +      status: StatusView.render("show.json", %{activity: activity, for: user}), +      created_at: Utils.to_masto_date(notification.inserted_at) +    } + +    assert expected == +             NotificationView.render("show.json", %{notification: notification, for: user})    end  end diff --git a/test/web/mastodon_api/views/poll_view_test.exs b/test/web/mastodon_api/views/poll_view_test.exs new file mode 100644 index 000000000..8cd7636a5 --- /dev/null +++ b/test/web/mastodon_api/views/poll_view_test.exs @@ -0,0 +1,126 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.MastodonAPI.PollViewTest do +  use Pleroma.DataCase + +  alias Pleroma.Object +  alias Pleroma.Web.CommonAPI +  alias Pleroma.Web.MastodonAPI.PollView + +  import Pleroma.Factory +  import Tesla.Mock + +  setup do +    mock(fn env -> apply(HttpRequestMock, :request, [env]) end) +    :ok +  end + +  test "renders a poll" do +    user = insert(:user) + +    {:ok, activity} = +      CommonAPI.post(user, %{ +        "status" => "Is Tenshi eating a corndog cute?", +        "poll" => %{ +          "options" => ["absolutely!", "sure", "yes", "why are you even asking?"], +          "expires_in" => 20 +        } +      }) + +    object = Object.normalize(activity) + +    expected = %{ +      emojis: [], +      expired: false, +      id: to_string(object.id), +      multiple: false, +      options: [ +        %{title: "absolutely!", votes_count: 0}, +        %{title: "sure", votes_count: 0}, +        %{title: "yes", votes_count: 0}, +        %{title: "why are you even asking?", votes_count: 0} +      ], +      voted: false, +      votes_count: 0 +    } + +    result = PollView.render("show.json", %{object: object}) +    expires_at = result.expires_at +    result = Map.delete(result, :expires_at) + +    assert result == expected + +    expires_at = NaiveDateTime.from_iso8601!(expires_at) +    assert NaiveDateTime.diff(expires_at, NaiveDateTime.utc_now()) in 15..20 +  end + +  test "detects if it is multiple choice" do +    user = insert(:user) + +    {:ok, activity} = +      CommonAPI.post(user, %{ +        "status" => "Which Mastodon developer is your favourite?", +        "poll" => %{ +          "options" => ["Gargron", "Eugen"], +          "expires_in" => 20, +          "multiple" => true +        } +      }) + +    object = Object.normalize(activity) + +    assert %{multiple: true} = PollView.render("show.json", %{object: object}) +  end + +  test "detects emoji" do +    user = insert(:user) + +    {:ok, activity} = +      CommonAPI.post(user, %{ +        "status" => "What's with the smug face?", +        "poll" => %{ +          "options" => [":blank: sip", ":blank::blank: sip", ":blank::blank::blank: sip"], +          "expires_in" => 20 +        } +      }) + +    object = Object.normalize(activity) + +    assert %{emojis: [%{shortcode: "blank"}]} = PollView.render("show.json", %{object: object}) +  end + +  test "detects vote status" do +    user = insert(:user) +    other_user = insert(:user) + +    {:ok, activity} = +      CommonAPI.post(user, %{ +        "status" => "Which input devices do you use?", +        "poll" => %{ +          "options" => ["mouse", "trackball", "trackpoint"], +          "multiple" => true, +          "expires_in" => 20 +        } +      }) + +    object = Object.normalize(activity) + +    {:ok, _, object} = CommonAPI.vote(other_user, object, [1, 2]) + +    result = PollView.render("show.json", %{object: object, for: other_user}) + +    assert result[:voted] == true +    assert Enum.at(result[:options], 1)[:votes_count] == 1 +    assert Enum.at(result[:options], 2)[:votes_count] == 1 +  end + +  test "does not crash on polls with no end date" do +    object = Object.normalize("https://skippers-bin.com/notes/7x9tmrp97i") +    result = PollView.render("show.json", %{object: object}) + +    assert result[:expires_at] == nil +    assert result[:expired] == false +  end +end diff --git a/test/web/mastodon_api/push_subscription_view_test.exs b/test/web/mastodon_api/views/push_subscription_view_test.exs index dc935fc82..4e4f5b7e6 100644 --- a/test/web/mastodon_api/push_subscription_view_test.exs +++ b/test/web/mastodon_api/views/push_subscription_view_test.exs @@ -1,5 +1,5 @@  # Pleroma: A lightweight social networking server -# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.Web.MastodonAPI.PushSubscriptionViewTest do diff --git a/test/web/mastodon_api/scheduled_activity_view_test.exs b/test/web/mastodon_api/views/scheduled_activity_view_test.exs index ecbb855d4..6387e4555 100644 --- a/test/web/mastodon_api/scheduled_activity_view_test.exs +++ b/test/web/mastodon_api/views/scheduled_activity_view_test.exs @@ -1,5 +1,5 @@  # Pleroma: A lightweight social networking server -# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.Web.MastodonAPI.ScheduledActivityViewTest do diff --git a/test/web/mastodon_api/status_view_test.exs b/test/web/mastodon_api/views/status_view_test.exs index 3447c5b1f..25777b011 100644 --- a/test/web/mastodon_api/status_view_test.exs +++ b/test/web/mastodon_api/views/status_view_test.exs @@ -1,5 +1,5 @@  # Pleroma: A lightweight social networking server -# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.Web.MastodonAPI.StatusViewTest do @@ -7,6 +7,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do    alias Pleroma.Activity    alias Pleroma.Bookmark +  alias Pleroma.Conversation.Participation +  alias Pleroma.HTML    alias Pleroma.Object    alias Pleroma.Repo    alias Pleroma.User @@ -14,7 +16,6 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do    alias Pleroma.Web.CommonAPI.Utils    alias Pleroma.Web.MastodonAPI.AccountView    alias Pleroma.Web.MastodonAPI.StatusView -  alias Pleroma.Web.OStatus    import Pleroma.Factory    import Tesla.Mock @@ -23,6 +24,59 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do      :ok    end +  test "has an emoji reaction list" do +    user = insert(:user) +    other_user = insert(:user) +    third_user = insert(:user) +    {:ok, activity} = CommonAPI.post(user, %{"status" => "dae cofe??"}) + +    {:ok, _, _} = CommonAPI.react_with_emoji(activity.id, user, "☕") +    {:ok, _, _} = CommonAPI.react_with_emoji(activity.id, third_user, "🍵") +    {:ok, _, _} = CommonAPI.react_with_emoji(activity.id, other_user, "☕") +    activity = Repo.get(Activity, activity.id) +    status = StatusView.render("show.json", activity: activity) + +    assert status[:pleroma][:emoji_reactions] == [ +             %{emoji: "☕", count: 2}, +             %{emoji: "🍵", count: 1} +           ] +  end + +  test "loads and returns the direct conversation id when given the `with_direct_conversation_id` option" do +    user = insert(:user) + +    {:ok, activity} = CommonAPI.post(user, %{"status" => "Hey @shp!", "visibility" => "direct"}) +    [participation] = Participation.for_user(user) + +    status = +      StatusView.render("show.json", +        activity: activity, +        with_direct_conversation_id: true, +        for: user +      ) + +    assert status[:pleroma][:direct_conversation_id] == participation.id + +    status = StatusView.render("show.json", activity: activity, for: user) +    assert status[:pleroma][:direct_conversation_id] == nil +  end + +  test "returns the direct conversation id when given the `direct_conversation_id` option" do +    user = insert(:user) + +    {:ok, activity} = CommonAPI.post(user, %{"status" => "Hey @shp!", "visibility" => "direct"}) +    [participation] = Participation.for_user(user) + +    status = +      StatusView.render("show.json", +        activity: activity, +        direct_conversation_id: participation.id, +        for: user +      ) + +    assert status[:pleroma][:direct_conversation_id] == participation.id +  end +    test "returns a temporary ap_id based user for activities missing db users" do      user = insert(:user) @@ -31,7 +85,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do      Repo.delete(user)      Cachex.clear(:user_cache) -    %{account: ms_user} = StatusView.render("status.json", activity: activity) +    %{account: ms_user} = StatusView.render("show.json", activity: activity)      assert ms_user.acct == "erroruser@example.com"    end @@ -48,7 +102,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do      Cachex.clear(:user_cache) -    result = StatusView.render("status.json", activity: activity) +    result = StatusView.render("show.json", activity: activity)      assert result[:account][:id] == to_string(user.id)    end @@ -66,7 +120,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do      User.get_cached_by_ap_id(note.data["actor"]) -    status = StatusView.render("status.json", %{activity: note}) +    status = StatusView.render("show.json", %{activity: note})      assert status.content == ""    end @@ -78,7 +132,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do      convo_id = Utils.context_to_conversation_id(object_data["context"]) -    status = StatusView.render("status.json", %{activity: note}) +    status = StatusView.render("show.json", %{activity: note})      created_at =        (object_data["published"] || "") @@ -88,12 +142,12 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do        id: to_string(note.id),        uri: object_data["id"],        url: Pleroma.Web.Router.Helpers.o_status_url(Pleroma.Web.Endpoint, :notice, note), -      account: AccountView.render("account.json", %{user: user}), +      account: AccountView.render("show.json", %{user: user}),        in_reply_to_id: nil,        in_reply_to_account_id: nil,        card: nil,        reblog: nil, -      content: HtmlSanitizeEx.basic_html(object_data["content"]), +      content: HTML.filter_tags(object_data["content"]),        created_at: created_at,        reblogs_count: 0,        replies_count: 0, @@ -105,7 +159,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do        pinned: false,        sensitive: false,        poll: nil, -      spoiler_text: HtmlSanitizeEx.basic_html(object_data["summary"]), +      spoiler_text: HTML.filter_tags(object_data["summary"]),        visibility: "public",        media_attachments: [],        mentions: [], @@ -132,8 +186,12 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do          local: true,          conversation_id: convo_id,          in_reply_to_account_acct: nil, -        content: %{"text/plain" => HtmlSanitizeEx.strip_tags(object_data["content"])}, -        spoiler_text: %{"text/plain" => HtmlSanitizeEx.strip_tags(object_data["summary"])} +        content: %{"text/plain" => HTML.strip_tags(object_data["content"])}, +        spoiler_text: %{"text/plain" => HTML.strip_tags(object_data["summary"])}, +        expires_at: nil, +        direct_conversation_id: nil, +        thread_muted: false, +        emoji_reactions: []        }      } @@ -144,27 +202,45 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do      user = insert(:user)      other_user = insert(:user) -    {:ok, user} = User.mute(user, other_user) +    {:ok, _user_relationships} = User.mute(user, other_user)      {:ok, activity} = CommonAPI.post(other_user, %{"status" => "test"}) -    status = StatusView.render("status.json", %{activity: activity}) +    status = StatusView.render("show.json", %{activity: activity})      assert status.muted == false -    status = StatusView.render("status.json", %{activity: activity, for: user}) +    status = StatusView.render("show.json", %{activity: activity, for: user})      assert status.muted == true    end +  test "tells if the message is thread muted" do +    user = insert(:user) +    other_user = insert(:user) + +    {:ok, _user_relationships} = User.mute(user, other_user) + +    {:ok, activity} = CommonAPI.post(other_user, %{"status" => "test"}) +    status = StatusView.render("show.json", %{activity: activity, for: user}) + +    assert status.pleroma.thread_muted == false + +    {:ok, activity} = CommonAPI.add_mute(user, activity) + +    status = StatusView.render("show.json", %{activity: activity, for: user}) + +    assert status.pleroma.thread_muted == true +  end +    test "tells if the status is bookmarked" do      user = insert(:user)      {:ok, activity} = CommonAPI.post(user, %{"status" => "Cute girls doing cute things"}) -    status = StatusView.render("status.json", %{activity: activity}) +    status = StatusView.render("show.json", %{activity: activity})      assert status.bookmarked == false -    status = StatusView.render("status.json", %{activity: activity, for: user}) +    status = StatusView.render("show.json", %{activity: activity, for: user})      assert status.bookmarked == false @@ -172,7 +248,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do      activity = Activity.get_by_id_with_object(activity.id) -    status = StatusView.render("status.json", %{activity: activity, for: user}) +    status = StatusView.render("show.json", %{activity: activity, for: user})      assert status.bookmarked == true    end @@ -184,7 +260,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do      {:ok, activity} =        CommonAPI.post(user, %{"status" => "he", "in_reply_to_status_id" => note.id}) -    status = StatusView.render("status.json", %{activity: activity}) +    status = StatusView.render("show.json", %{activity: activity})      assert status.in_reply_to_id == to_string(note.id) @@ -194,17 +270,15 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do    end    test "contains mentions" do -    incoming = File.read!("test/fixtures/incoming_reply_mastodon.xml") -    # a user with this ap id might be in the cache. -    recipient = "https://pleroma.soykaf.com/users/lain" -    user = insert(:user, %{ap_id: recipient}) +    user = insert(:user) +    mentioned = insert(:user) -    {:ok, [activity]} = OStatus.handle_incoming(incoming) +    {:ok, activity} = CommonAPI.post(user, %{"status" => "hi @#{mentioned.nickname}"}) -    status = StatusView.render("status.json", %{activity: activity}) +    status = StatusView.render("show.json", %{activity: activity})      assert status.mentions == -             Enum.map([user], fn u -> AccountView.render("mention.json", %{user: u}) end) +             Enum.map([mentioned], fn u -> AccountView.render("mention.json", %{user: u}) end)    end    test "create mentions from the 'to' field" do @@ -227,7 +301,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do      assert length(activity.recipients) == 3 -    %{mentions: [mention] = mentions} = StatusView.render("status.json", %{activity: activity}) +    %{mentions: [mention] = mentions} = StatusView.render("show.json", %{activity: activity})      assert length(mentions) == 1      assert mention.url == recipient_ap_id @@ -264,7 +338,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do      assert length(activity.recipients) == 3 -    %{mentions: [mention] = mentions} = StatusView.render("status.json", %{activity: activity}) +    %{mentions: [mention] = mentions} = StatusView.render("show.json", %{activity: activity})      assert length(mentions) == 1      assert mention.url == recipient.ap_id @@ -300,13 +374,23 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do      assert %{id: "2"} = StatusView.render("attachment.json", %{attachment: object})    end +  test "put the url advertised in the Activity in to the url attribute" do +    id = "https://wedistribute.org/wp-json/pterotype/v1/object/85810" +    [activity] = Activity.search(nil, id) + +    status = StatusView.render("show.json", %{activity: activity}) + +    assert status.uri == id +    assert status.url == "https://wedistribute.org/2019/07/mastodon-drops-ostatus/" +  end +    test "a reblog" do      user = insert(:user)      activity = insert(:note_activity)      {:ok, reblog, _} = CommonAPI.repeat(activity.id, user) -    represented = StatusView.render("status.json", %{for: user, activity: reblog}) +    represented = StatusView.render("show.json", %{for: user, activity: reblog})      assert represented[:id] == to_string(reblog.id)      assert represented[:reblog][:id] == to_string(activity.id) @@ -323,12 +407,27 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do      %Activity{} = activity = Activity.get_create_by_object_ap_id(object.data["id"]) -    represented = StatusView.render("status.json", %{for: user, activity: activity}) +    represented = StatusView.render("show.json", %{for: user, activity: activity})      assert represented[:id] == to_string(activity.id)      assert length(represented[:media_attachments]) == 1    end +  test "a Mobilizon event" do +    user = insert(:user) + +    {:ok, object} = +      Pleroma.Object.Fetcher.fetch_object_from_id( +        "https://mobilizon.org/events/252d5816-00a3-4a89-a66f-15bf65c33e39" +      ) + +    %Activity{} = activity = Activity.get_create_by_object_ap_id(object.data["id"]) + +    represented = StatusView.render("show.json", %{for: user, activity: activity}) + +    assert represented[:id] == to_string(activity.id) +  end +    describe "build_tags/1" do      test "it returns a a dictionary tags" do        object_tags = [ @@ -405,108 +504,6 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do      end    end -  describe "poll view" do -    test "renders a poll" do -      user = insert(:user) - -      {:ok, activity} = -        CommonAPI.post(user, %{ -          "status" => "Is Tenshi eating a corndog cute?", -          "poll" => %{ -            "options" => ["absolutely!", "sure", "yes", "why are you even asking?"], -            "expires_in" => 20 -          } -        }) - -      object = Object.normalize(activity) - -      expected = %{ -        emojis: [], -        expired: false, -        id: to_string(object.id), -        multiple: false, -        options: [ -          %{title: "absolutely!", votes_count: 0}, -          %{title: "sure", votes_count: 0}, -          %{title: "yes", votes_count: 0}, -          %{title: "why are you even asking?", votes_count: 0} -        ], -        voted: false, -        votes_count: 0 -      } - -      result = StatusView.render("poll.json", %{object: object}) -      expires_at = result.expires_at -      result = Map.delete(result, :expires_at) - -      assert result == expected - -      expires_at = NaiveDateTime.from_iso8601!(expires_at) -      assert NaiveDateTime.diff(expires_at, NaiveDateTime.utc_now()) in 15..20 -    end - -    test "detects if it is multiple choice" do -      user = insert(:user) - -      {:ok, activity} = -        CommonAPI.post(user, %{ -          "status" => "Which Mastodon developer is your favourite?", -          "poll" => %{ -            "options" => ["Gargron", "Eugen"], -            "expires_in" => 20, -            "multiple" => true -          } -        }) - -      object = Object.normalize(activity) - -      assert %{multiple: true} = StatusView.render("poll.json", %{object: object}) -    end - -    test "detects emoji" do -      user = insert(:user) - -      {:ok, activity} = -        CommonAPI.post(user, %{ -          "status" => "What's with the smug face?", -          "poll" => %{ -            "options" => [":blank: sip", ":blank::blank: sip", ":blank::blank::blank: sip"], -            "expires_in" => 20 -          } -        }) - -      object = Object.normalize(activity) - -      assert %{emojis: [%{shortcode: "blank"}]} = -               StatusView.render("poll.json", %{object: object}) -    end - -    test "detects vote status" do -      user = insert(:user) -      other_user = insert(:user) - -      {:ok, activity} = -        CommonAPI.post(user, %{ -          "status" => "Which input devices do you use?", -          "poll" => %{ -            "options" => ["mouse", "trackball", "trackpoint"], -            "multiple" => true, -            "expires_in" => 20 -          } -        }) - -      object = Object.normalize(activity) - -      {:ok, _, object} = CommonAPI.vote(other_user, object, [1, 2]) - -      result = StatusView.render("poll.json", %{object: object, for: other_user}) - -      assert result[:voted] == true -      assert Enum.at(result[:options], 1)[:votes_count] == 1 -      assert Enum.at(result[:options], 2)[:votes_count] == 1 -    end -  end -    test "embeds a relationship in the account" do      user = insert(:user)      other_user = insert(:user) @@ -516,7 +513,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do          "status" => "drink more water"        }) -    result = StatusView.render("status.json", %{activity: activity, for: other_user}) +    result = StatusView.render("show.json", %{activity: activity, for: other_user})      assert result[:account][:pleroma][:relationship] ==               AccountView.render("relationship.json", %{user: other_user, target: user}) @@ -533,7 +530,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do      {:ok, activity, _object} = CommonAPI.repeat(activity.id, other_user) -    result = StatusView.render("status.json", %{activity: activity, for: user}) +    result = StatusView.render("show.json", %{activity: activity, for: user})      assert result[:account][:pleroma][:relationship] ==               AccountView.render("relationship.json", %{user: user, target: other_user}) @@ -550,8 +547,17 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do      {:ok, activity} =        CommonAPI.post(user, %{"status" => "foobar", "visibility" => "list:#{list.id}"}) -    status = StatusView.render("status.json", activity: activity) +    status = StatusView.render("show.json", activity: activity)      assert status.visibility == "list"    end + +  test "successfully renders a Listen activity (pleroma extension)" do +    listen_activity = insert(:listen) + +    status = StatusView.render("listen.json", activity: listen_activity) + +    assert status.length == listen_activity.data["object"]["length"] +    assert status.title == listen_activity.data["object"]["title"] +  end  end diff --git a/test/web/media_proxy/media_proxy_controller_test.exs b/test/web/media_proxy/media_proxy_controller_test.exs index 53b8f556b..fdfdb5ec6 100644 --- a/test/web/media_proxy/media_proxy_controller_test.exs +++ b/test/web/media_proxy/media_proxy_controller_test.exs @@ -1,5 +1,5 @@  # Pleroma: A lightweight social networking server -# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.Web.MediaProxy.MediaProxyControllerTest do diff --git a/test/web/media_proxy/media_proxy_test.exs b/test/web/media_proxy/media_proxy_test.exs index edbbf9b66..96bdde219 100644 --- a/test/web/media_proxy/media_proxy_test.exs +++ b/test/web/media_proxy/media_proxy_test.exs @@ -1,17 +1,14 @@  # Pleroma: A lightweight social networking server -# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.Web.MediaProxyTest do    use ExUnit.Case +  use Pleroma.Tests.Helpers    import Pleroma.Web.MediaProxy    alias Pleroma.Web.MediaProxy.MediaProxyController -  setup do -    enabled = Pleroma.Config.get([:media_proxy, :enabled]) -    on_exit(fn -> Pleroma.Config.put([:media_proxy, :enabled], enabled) end) -    :ok -  end +  clear_config([:media_proxy, :enabled])    describe "when enabled" do      setup do @@ -171,21 +168,6 @@ defmodule Pleroma.Web.MediaProxyTest do        encoded = url(url)        assert decode_result(encoded) == url      end - -    test "does not change whitelisted urls" do -      upload_config = Pleroma.Config.get([Pleroma.Upload]) -      media_url = "https://media.pleroma.social" -      Pleroma.Config.put([Pleroma.Upload, :base_url], media_url) -      Pleroma.Config.put([:media_proxy, :whitelist], ["media.pleroma.social"]) -      Pleroma.Config.put([:media_proxy, :base_url], "https://cache.pleroma.social") - -      url = "#{media_url}/static/logo.png" -      encoded = url(url) - -      assert String.starts_with?(encoded, media_url) - -      Pleroma.Config.put([Pleroma.Upload], upload_config) -    end    end    describe "when disabled" do @@ -215,12 +197,43 @@ defmodule Pleroma.Web.MediaProxyTest do      decoded    end -  test "mediaproxy whitelist" do -    Pleroma.Config.put([:media_proxy, :enabled], true) -    Pleroma.Config.put([:media_proxy, :whitelist], ["google.com", "feld.me"]) -    url = "https://feld.me/foo.png" +  describe "whitelist" do +    setup do +      Pleroma.Config.put([:media_proxy, :enabled], true) +      :ok +    end + +    test "mediaproxy whitelist" do +      Pleroma.Config.put([:media_proxy, :whitelist], ["google.com", "feld.me"]) +      url = "https://feld.me/foo.png" + +      unencoded = url(url) +      assert unencoded == url +    end -    unencoded = url(url) -    assert unencoded == url +    test "does not change whitelisted urls" do +      Pleroma.Config.put([:media_proxy, :whitelist], ["mycdn.akamai.com"]) +      Pleroma.Config.put([:media_proxy, :base_url], "https://cache.pleroma.social") + +      media_url = "https://mycdn.akamai.com" + +      url = "#{media_url}/static/logo.png" +      encoded = url(url) + +      assert String.starts_with?(encoded, media_url) +    end + +    test "ensure Pleroma.Upload base_url is always whitelisted" do +      upload_config = Pleroma.Config.get([Pleroma.Upload]) +      media_url = "https://media.pleroma.social" +      Pleroma.Config.put([Pleroma.Upload, :base_url], media_url) + +      url = "#{media_url}/static/logo.png" +      encoded = url(url) + +      assert String.starts_with?(encoded, media_url) + +      Pleroma.Config.put([Pleroma.Upload], upload_config) +    end    end  end diff --git a/test/web/metadata/feed_test.exs b/test/web/metadata/feed_test.exs new file mode 100644 index 000000000..50e9ce52e --- /dev/null +++ b/test/web/metadata/feed_test.exs @@ -0,0 +1,18 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.Metadata.Providers.FeedTest do +  use Pleroma.DataCase +  import Pleroma.Factory +  alias Pleroma.Web.Metadata.Providers.Feed + +  test "it renders a link to user's atom feed" do +    user = insert(:user, nickname: "lain") + +    assert Feed.build_tags(%{user: user}) == [ +             {:link, +              [rel: "alternate", type: "application/atom+xml", href: "/users/lain/feed.atom"], []} +           ] +  end +end diff --git a/test/web/metadata/twitter_card_test.exs b/test/web/metadata/twitter_card_test.exs index 0814006d2..85a654f52 100644 --- a/test/web/metadata/twitter_card_test.exs +++ b/test/web/metadata/twitter_card_test.exs @@ -26,7 +26,32 @@ defmodule Pleroma.Web.Metadata.Providers.TwitterCardTest do             ]    end -  test "it does not render attachments if post is nsfw" do +  test "it uses summary twittercard if post has no attachment" do +    user = insert(:user, name: "Jimmy Hendriks", bio: "born 19 March 1994") +    {:ok, activity} = CommonAPI.post(user, %{"status" => "HI"}) + +    note = +      insert(:note, %{ +        data: %{ +          "actor" => user.ap_id, +          "tag" => [], +          "id" => "https://pleroma.gov/objects/whatever", +          "content" => "pleroma in a nutshell" +        } +      }) + +    result = TwitterCard.build_tags(%{object: note, user: user, activity_id: activity.id}) + +    assert [ +             {:meta, [property: "twitter:title", content: Utils.user_name_string(user)], []}, +             {:meta, [property: "twitter:description", content: "“pleroma in a nutshell”"], []}, +             {:meta, [property: "twitter:image", content: "http://localhost:4001/images/avi.png"], +              []}, +             {:meta, [property: "twitter:card", content: "summary"], []} +           ] == result +  end + +  test "it renders avatar not attachment if post is nsfw and unfurl_nsfw is disabled" do      Pleroma.Config.put([Pleroma.Web.Metadata, :unfurl_nsfw], false)      user = insert(:user, name: "Jimmy Hendriks", bio: "born 19 March 1994")      {:ok, activity} = CommonAPI.post(user, %{"status" => "HI"}) @@ -67,7 +92,7 @@ defmodule Pleroma.Web.Metadata.Providers.TwitterCardTest do               {:meta, [property: "twitter:description", content: "“pleroma in a nutshell”"], []},               {:meta, [property: "twitter:image", content: "http://localhost:4001/images/avi.png"],                []}, -             {:meta, [property: "twitter:card", content: "summary_large_image"], []} +             {:meta, [property: "twitter:card", content: "summary"], []}             ] == result    end diff --git a/test/web/metadata/utils_test.exs b/test/web/metadata/utils_test.exs new file mode 100644 index 000000000..7547f2932 --- /dev/null +++ b/test/web/metadata/utils_test.exs @@ -0,0 +1,32 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.Metadata.UtilsTest do +  use Pleroma.DataCase +  import Pleroma.Factory +  alias Pleroma.Web.Metadata.Utils + +  describe "scrub_html_and_truncate/1" do +    test "it returns text without encode HTML" do +      user = insert(:user) + +      note = +        insert(:note, %{ +          data: %{ +            "actor" => user.ap_id, +            "id" => "https://pleroma.gov/objects/whatever", +            "content" => "Pleroma's really cool!" +          } +        }) + +      assert Utils.scrub_html_and_truncate(note) == "Pleroma's really cool!" +    end +  end + +  describe "scrub_html_and_truncate/2" do +    test "it returns text without encode HTML" do +      assert Utils.scrub_html_and_truncate("Pleroma's really cool!") == "Pleroma's really cool!" +    end +  end +end diff --git a/test/web/node_info_test.exs b/test/web/node_info_test.exs index d7f848bfa..9a574a38d 100644 --- a/test/web/node_info_test.exs +++ b/test/web/node_info_test.exs @@ -1,5 +1,5 @@  # Pleroma: A lightweight social networking server -# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.Web.NodeInfoTest do @@ -24,8 +24,8 @@ defmodule Pleroma.Web.NodeInfoTest do    end    test "nodeinfo shows staff accounts", %{conn: conn} do -    moderator = insert(:user, %{local: true, info: %{is_moderator: true}}) -    admin = insert(:user, %{local: true, info: %{is_admin: true}}) +    moderator = insert(:user, local: true, is_moderator: true) +    admin = insert(:user, local: true, is_admin: true)      conn =        conn @@ -61,6 +61,33 @@ defmodule Pleroma.Web.NodeInfoTest do      assert Pleroma.Application.repository() == result["software"]["repository"]    end +  test "returns fieldsLimits field", %{conn: conn} do +    max_account_fields = Pleroma.Config.get([:instance, :max_account_fields]) +    max_remote_account_fields = Pleroma.Config.get([:instance, :max_remote_account_fields]) +    account_field_name_length = Pleroma.Config.get([:instance, :account_field_name_length]) +    account_field_value_length = Pleroma.Config.get([:instance, :account_field_value_length]) + +    Pleroma.Config.put([:instance, :max_account_fields], 10) +    Pleroma.Config.put([:instance, :max_remote_account_fields], 15) +    Pleroma.Config.put([:instance, :account_field_name_length], 255) +    Pleroma.Config.put([:instance, :account_field_value_length], 2048) + +    response = +      conn +      |> get("/nodeinfo/2.1.json") +      |> json_response(:ok) + +    assert response["metadata"]["fieldsLimits"]["maxFields"] == 10 +    assert response["metadata"]["fieldsLimits"]["maxRemoteFields"] == 15 +    assert response["metadata"]["fieldsLimits"]["nameLength"] == 255 +    assert response["metadata"]["fieldsLimits"]["valueLength"] == 2048 + +    Pleroma.Config.put([:instance, :max_account_fields], max_account_fields) +    Pleroma.Config.put([:instance, :max_remote_account_fields], max_remote_account_fields) +    Pleroma.Config.put([:instance, :account_field_name_length], account_field_name_length) +    Pleroma.Config.put([:instance, :account_field_value_length], account_field_value_length) +  end +    test "it returns the safe_dm_mentions feature if enabled", %{conn: conn} do      option = Pleroma.Config.get([:instance, :safe_dm_mentions])      Pleroma.Config.put([:instance, :safe_dm_mentions], true) @@ -84,7 +111,34 @@ defmodule Pleroma.Web.NodeInfoTest do      Pleroma.Config.put([:instance, :safe_dm_mentions], option)    end +  test "it shows if federation is enabled/disabled", %{conn: conn} do +    original = Pleroma.Config.get([:instance, :federating]) + +    Pleroma.Config.put([:instance, :federating], true) + +    response = +      conn +      |> get("/nodeinfo/2.1.json") +      |> json_response(:ok) + +    assert response["metadata"]["federation"]["enabled"] == true + +    Pleroma.Config.put([:instance, :federating], false) + +    response = +      conn +      |> get("/nodeinfo/2.1.json") +      |> json_response(:ok) + +    assert response["metadata"]["federation"]["enabled"] == false + +    Pleroma.Config.put([:instance, :federating], original) +  end +    test "it shows MRF transparency data if enabled", %{conn: conn} do +    config = Pleroma.Config.get([:instance, :rewrite_policy]) +    Pleroma.Config.put([:instance, :rewrite_policy], [Pleroma.Web.ActivityPub.MRF.SimplePolicy]) +      option = Pleroma.Config.get([:instance, :mrf_transparency])      Pleroma.Config.put([:instance, :mrf_transparency], true) @@ -98,11 +152,15 @@ defmodule Pleroma.Web.NodeInfoTest do      assert response["metadata"]["federation"]["mrf_simple"] == simple_config +    Pleroma.Config.put([:instance, :rewrite_policy], config)      Pleroma.Config.put([:instance, :mrf_transparency], option)      Pleroma.Config.put(:mrf_simple, %{})    end    test "it performs exclusions from MRF transparency data if configured", %{conn: conn} do +    config = Pleroma.Config.get([:instance, :rewrite_policy]) +    Pleroma.Config.put([:instance, :rewrite_policy], [Pleroma.Web.ActivityPub.MRF.SimplePolicy]) +      option = Pleroma.Config.get([:instance, :mrf_transparency])      Pleroma.Config.put([:instance, :mrf_transparency], true) @@ -122,6 +180,7 @@ defmodule Pleroma.Web.NodeInfoTest do      assert response["metadata"]["federation"]["mrf_simple"] == expected_config      assert response["metadata"]["federation"]["exclusions"] == true +    Pleroma.Config.put([:instance, :rewrite_policy], config)      Pleroma.Config.put([:instance, :mrf_transparency], option)      Pleroma.Config.put([:instance, :mrf_transparency_exclusions], exclusions)      Pleroma.Config.put(:mrf_simple, %{}) diff --git a/test/web/oauth/app_test.exs b/test/web/oauth/app_test.exs new file mode 100644 index 000000000..195b8c17f --- /dev/null +++ b/test/web/oauth/app_test.exs @@ -0,0 +1,33 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.OAuth.AppTest do +  use Pleroma.DataCase + +  alias Pleroma.Web.OAuth.App +  import Pleroma.Factory + +  describe "get_or_make/2" do +    test "gets exist app" do +      attrs = %{client_name: "Mastodon-Local", redirect_uris: "."} +      app = insert(:oauth_app, Map.merge(attrs, %{scopes: ["read", "write"]})) +      {:ok, %App{} = exist_app} = App.get_or_make(attrs, []) +      assert exist_app == app +    end + +    test "make app" do +      attrs = %{client_name: "Mastodon-Local", redirect_uris: "."} +      {:ok, %App{} = app} = App.get_or_make(attrs, ["write"]) +      assert app.scopes == ["write"] +    end + +    test "gets exist app and updates scopes" do +      attrs = %{client_name: "Mastodon-Local", redirect_uris: "."} +      app = insert(:oauth_app, Map.merge(attrs, %{scopes: ["read", "write"]})) +      {:ok, %App{} = exist_app} = App.get_or_make(attrs, ["read", "write", "follow", "push"]) +      assert exist_app.id == app.id +      assert exist_app.scopes == ["read", "write", "follow", "push"] +    end +  end +end diff --git a/test/web/oauth/authorization_test.exs b/test/web/oauth/authorization_test.exs index d8b008437..2e82a7b79 100644 --- a/test/web/oauth/authorization_test.exs +++ b/test/web/oauth/authorization_test.exs @@ -1,5 +1,5 @@  # Pleroma: A lightweight social networking server -# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.Web.OAuth.AuthorizationTest do diff --git a/test/web/oauth/ldap_authorization_test.exs b/test/web/oauth/ldap_authorization_test.exs index 0eb191c76..1cbe133b7 100644 --- a/test/web/oauth/ldap_authorization_test.exs +++ b/test/web/oauth/ldap_authorization_test.exs @@ -12,21 +12,12 @@ defmodule Pleroma.Web.OAuth.LDAPAuthorizationTest do    @skip if !Code.ensure_loaded?(:eldap), do: :skip -  setup_all do -    ldap_authenticator = -      Pleroma.Config.get(Pleroma.Web.Auth.Authenticator, Pleroma.Web.Auth.PleromaAuthenticator) - -    ldap_enabled = Pleroma.Config.get([:ldap, :enabled]) - -    on_exit(fn -> -      Pleroma.Config.put(Pleroma.Web.Auth.Authenticator, ldap_authenticator) -      Pleroma.Config.put([:ldap, :enabled], ldap_enabled) -    end) - -    Pleroma.Config.put(Pleroma.Web.Auth.Authenticator, Pleroma.Web.Auth.LDAPAuthenticator) +  clear_config_all([:ldap, :enabled]) do      Pleroma.Config.put([:ldap, :enabled], true) +  end -    :ok +  clear_config_all(Pleroma.Web.Auth.Authenticator) do +    Pleroma.Config.put(Pleroma.Web.Auth.Authenticator, Pleroma.Web.Auth.LDAPAuthenticator)    end    @tag @skip diff --git a/test/web/oauth/oauth_controller_test.exs b/test/web/oauth/oauth_controller_test.exs index aae34804d..adeff8e25 100644 --- a/test/web/oauth/oauth_controller_test.exs +++ b/test/web/oauth/oauth_controller_test.exs @@ -1,35 +1,26 @@  # Pleroma: A lightweight social networking server -# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.Web.OAuth.OAuthControllerTest do    use Pleroma.Web.ConnCase    import Pleroma.Factory -  import Mock -  alias Pleroma.Registration    alias Pleroma.Repo +  alias Pleroma.User    alias Pleroma.Web.OAuth.Authorization    alias Pleroma.Web.OAuth.OAuthController    alias Pleroma.Web.OAuth.Token -  @oauth_config_path [:oauth2, :issue_new_refresh_token]    @session_opts [      store: :cookie,      key: "_test",      signing_salt: "cooldude"    ] +  clear_config_all([:instance, :account_activation_required])    describe "in OAuth consumer mode, " do      setup do -      oauth_consumer_strategies_path = [:auth, :oauth_consumer_strategies] -      oauth_consumer_strategies = Pleroma.Config.get(oauth_consumer_strategies_path) -      Pleroma.Config.put(oauth_consumer_strategies_path, ~w(twitter facebook)) - -      on_exit(fn -> -        Pleroma.Config.put(oauth_consumer_strategies_path, oauth_consumer_strategies) -      end) -        [          app: insert(:oauth_app),          conn: @@ -39,6 +30,13 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do        ]      end +    clear_config([:auth, :oauth_consumer_strategies]) do +      Pleroma.Config.put( +        [:auth, :oauth_consumer_strategies], +        ~w(twitter facebook) +      ) +    end +      test "GET /oauth/authorize renders auth forms, including OAuth consumer form", %{        app: app,        conn: conn @@ -108,28 +106,26 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do          "state" => ""        } -      with_mock Pleroma.Web.Auth.Authenticator, -        get_registration: fn _ -> {:ok, registration} end do -        conn = -          get( -            conn, -            "/oauth/twitter/callback", -            %{ -              "oauth_token" => "G-5a3AAAAAAAwMH9AAABaektfSM", -              "oauth_verifier" => "QZl8vUqNvXMTKpdmUnGejJxuHG75WWWs", -              "provider" => "twitter", -              "state" => Poison.encode!(state_params) -            } -          ) +      conn = +        conn +        |> assign(:ueberauth_auth, %{provider: registration.provider, uid: registration.uid}) +        |> get( +          "/oauth/twitter/callback", +          %{ +            "oauth_token" => "G-5a3AAAAAAAwMH9AAABaektfSM", +            "oauth_verifier" => "QZl8vUqNvXMTKpdmUnGejJxuHG75WWWs", +            "provider" => "twitter", +            "state" => Poison.encode!(state_params) +          } +        ) -        assert response = html_response(conn, 302) -        assert redirected_to(conn) =~ ~r/#{redirect_uri}\?code=.+/ -      end +      assert response = html_response(conn, 302) +      assert redirected_to(conn) =~ ~r/#{redirect_uri}\?code=.+/      end      test "with user-unbound registration, GET /oauth/<provider>/callback renders registration_details page",           %{app: app, conn: conn} do -      registration = insert(:registration, user: nil) +      user = insert(:user)        state_params = %{          "scope" => "read write", @@ -138,26 +134,28 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do          "state" => "a_state"        } -      with_mock Pleroma.Web.Auth.Authenticator, -        get_registration: fn _ -> {:ok, registration} end do -        conn = -          get( -            conn, -            "/oauth/twitter/callback", -            %{ -              "oauth_token" => "G-5a3AAAAAAAwMH9AAABaektfSM", -              "oauth_verifier" => "QZl8vUqNvXMTKpdmUnGejJxuHG75WWWs", -              "provider" => "twitter", -              "state" => Poison.encode!(state_params) -            } -          ) +      conn = +        conn +        |> assign(:ueberauth_auth, %{ +          provider: "twitter", +          uid: "171799000", +          info: %{nickname: user.nickname, email: user.email, name: user.name, description: nil} +        }) +        |> get( +          "/oauth/twitter/callback", +          %{ +            "oauth_token" => "G-5a3AAAAAAAwMH9AAABaektfSM", +            "oauth_verifier" => "QZl8vUqNvXMTKpdmUnGejJxuHG75WWWs", +            "provider" => "twitter", +            "state" => Poison.encode!(state_params) +          } +        ) -        assert response = html_response(conn, 200) -        assert response =~ ~r/name="op" type="submit" value="register"/ -        assert response =~ ~r/name="op" type="submit" value="connect"/ -        assert response =~ Registration.email(registration) -        assert response =~ Registration.nickname(registration) -      end +      assert response = html_response(conn, 200) +      assert response =~ ~r/name="op" type="submit" value="register"/ +      assert response =~ ~r/name="op" type="submit" value="connect"/ +      assert response =~ user.email +      assert response =~ user.nickname      end      test "on authentication error, GET /oauth/<provider>/callback redirects to `redirect_uri`", %{ @@ -452,7 +450,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do      test "renders authentication page if user is already authenticated but `force_login` is tru-ish",           %{app: app, conn: conn} do -      token = insert(:oauth_token, app_id: app.id) +      token = insert(:oauth_token, app: app)        conn =          conn @@ -471,12 +469,35 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do        assert html_response(conn, 200) =~ ~s(type="submit")      end +    test "renders authentication page if user is already authenticated but user request with another client", +         %{ +           app: app, +           conn: conn +         } do +      token = insert(:oauth_token, app: app) + +      conn = +        conn +        |> put_session(:oauth_token, token.token) +        |> get( +          "/oauth/authorize", +          %{ +            "response_type" => "code", +            "client_id" => "another_client_id", +            "redirect_uri" => OAuthController.default_redirect_uri(app), +            "scope" => "read" +          } +        ) + +      assert html_response(conn, 200) =~ ~s(type="submit") +    end +      test "with existing authentication and non-OOB `redirect_uri`, redirects to app with `token` and `state` params",           %{             app: app,             conn: conn           } do -      token = insert(:oauth_token, app_id: app.id) +      token = insert(:oauth_token, app: app)        conn =          conn @@ -502,7 +523,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do             conn: conn           } do        unlisted_redirect_uri = "http://cross-site-request.com" -      token = insert(:oauth_token, app_id: app.id) +      token = insert(:oauth_token, app: app)        conn =          conn @@ -526,7 +547,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do             app: app,             conn: conn           } do -      token = insert(:oauth_token, app_id: app.id) +      token = insert(:oauth_token, app: app)        conn =          conn @@ -546,33 +567,46 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do    end    describe "POST /oauth/authorize" do -    test "redirects with oauth authorization" do -      user = insert(:user) -      app = insert(:oauth_app, scopes: ["read", "write", "follow"]) +    test "redirects with oauth authorization, " <> +           "granting requested app-supported scopes to both admin- and non-admin users" do +      app_scopes = ["read", "write", "admin", "secret_scope"] +      app = insert(:oauth_app, scopes: app_scopes)        redirect_uri = OAuthController.default_redirect_uri(app) -      conn = -        build_conn() -        |> post("/oauth/authorize", %{ -          "authorization" => %{ -            "name" => user.nickname, -            "password" => "test", -            "client_id" => app.client_id, -            "redirect_uri" => redirect_uri, -            "scope" => "read write", -            "state" => "statepassed" -          } -        }) +      non_admin = insert(:user, is_admin: false) +      admin = insert(:user, is_admin: true) +      scopes_subset = ["read:subscope", "write", "admin"] -      target = redirected_to(conn) -      assert target =~ redirect_uri +      # In case scope param is missing, expecting _all_ app-supported scopes to be granted +      for user <- [non_admin, admin], +          {requested_scopes, expected_scopes} <- +            %{scopes_subset => scopes_subset, nil => app_scopes} do +        conn = +          post( +            build_conn(), +            "/oauth/authorize", +            %{ +              "authorization" => %{ +                "name" => user.nickname, +                "password" => "test", +                "client_id" => app.client_id, +                "redirect_uri" => redirect_uri, +                "scope" => requested_scopes, +                "state" => "statepassed" +              } +            } +          ) -      query = URI.parse(target).query |> URI.query_decoder() |> Map.new() +        target = redirected_to(conn) +        assert target =~ redirect_uri -      assert %{"state" => "statepassed", "code" => code} = query -      auth = Repo.get_by(Authorization, token: code) -      assert auth -      assert auth.scopes == ["read", "write"] +        query = URI.parse(target).query |> URI.query_decoder() |> Map.new() + +        assert %{"state" => "statepassed", "code" => code} = query +        auth = Repo.get_by(Authorization, token: code) +        assert auth +        assert auth.scopes == expected_scopes +      end      end      test "returns 401 for wrong credentials", %{conn: conn} do @@ -602,13 +636,13 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do        assert result =~ "Invalid Username/Password"      end -    test "returns 401 for missing scopes", %{conn: conn} do -      user = insert(:user) -      app = insert(:oauth_app) +    test "returns 401 for missing scopes" do +      user = insert(:user, is_admin: false) +      app = insert(:oauth_app, scopes: ["read", "write", "admin"])        redirect_uri = OAuthController.default_redirect_uri(app)        result = -        conn +        build_conn()          |> post("/oauth/authorize", %{            "authorization" => %{              "name" => user.nickname, @@ -629,7 +663,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do        assert result =~ "This action is outside the authorized scopes"      end -    test "returns 401 for scopes beyond app scopes", %{conn: conn} do +    test "returns 401 for scopes beyond app scopes hierarchy", %{conn: conn} do        user = insert(:user)        app = insert(:oauth_app, scopes: ["read", "write"])        redirect_uri = OAuthController.default_redirect_uri(app) @@ -777,24 +811,15 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do      end      test "rejects token exchange for valid credentials belonging to unconfirmed user and confirmation is required" do -      setting = Pleroma.Config.get([:instance, :account_activation_required]) - -      unless setting do -        Pleroma.Config.put([:instance, :account_activation_required], true) -        on_exit(fn -> Pleroma.Config.put([:instance, :account_activation_required], setting) end) -      end - +      Pleroma.Config.put([:instance, :account_activation_required], true)        password = "testpassword" -      user = insert(:user, password_hash: Comeonin.Pbkdf2.hashpwsalt(password)) -      info_change = Pleroma.User.Info.confirmation_changeset(user.info, need_confirmation: true)        {:ok, user} = -        user -        |> Ecto.Changeset.change() -        |> Ecto.Changeset.put_embed(:info, info_change) -        |> Repo.update() +        insert(:user, password_hash: Comeonin.Pbkdf2.hashpwsalt(password)) +        |> User.confirmation_changeset(need_confirmation: true) +        |> User.update_and_set_cache() -      refute Pleroma.User.auth_active?(user) +      refute Pleroma.User.account_status(user) == :active        app = insert(:oauth_app) @@ -819,12 +844,12 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do        user =          insert(:user,            password_hash: Comeonin.Pbkdf2.hashpwsalt(password), -          info: %{deactivated: true} +          deactivated: true          )        app = insert(:oauth_app) -      conn = +      resp =          build_conn()          |> post("/oauth/token", %{            "grant_type" => "password", @@ -833,10 +858,69 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do            "client_id" => app.client_id,            "client_secret" => app.client_secret          }) +        |> json_response(403) -      assert resp = json_response(conn, 403) -      assert %{"error" => _} = resp -      refute Map.has_key?(resp, "access_token") +      assert resp == %{ +               "error" => "Your account is currently disabled", +               "identifier" => "account_is_disabled" +             } +    end + +    test "rejects token exchange for user with password_reset_pending set to true" do +      password = "testpassword" + +      user = +        insert(:user, +          password_hash: Comeonin.Pbkdf2.hashpwsalt(password), +          password_reset_pending: true +        ) + +      app = insert(:oauth_app, scopes: ["read", "write"]) + +      resp = +        build_conn() +        |> post("/oauth/token", %{ +          "grant_type" => "password", +          "username" => user.nickname, +          "password" => password, +          "client_id" => app.client_id, +          "client_secret" => app.client_secret +        }) +        |> json_response(403) + +      assert resp == %{ +               "error" => "Password reset is required", +               "identifier" => "password_reset_required" +             } +    end + +    test "rejects token exchange for user with confirmation_pending set to true" do +      Pleroma.Config.put([:instance, :account_activation_required], true) +      password = "testpassword" + +      user = +        insert(:user, +          password_hash: Comeonin.Pbkdf2.hashpwsalt(password), +          confirmation_pending: true +        ) + +      app = insert(:oauth_app, scopes: ["read", "write"]) + +      resp = +        build_conn() +        |> post("/oauth/token", %{ +          "grant_type" => "password", +          "username" => user.nickname, +          "password" => password, +          "client_id" => app.client_id, +          "client_secret" => app.client_secret +        }) +        |> json_response(403) + +      assert resp == %{ +               "error" => "Your login is missing a confirmed e-mail address", +               "identifier" => "missing_confirmed_email" +             }      end      test "rejects an invalid authorization code" do @@ -859,16 +943,10 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do    end    describe "POST /oauth/token - refresh token" do -    setup do -      oauth_token_config = Pleroma.Config.get(@oauth_config_path) - -      on_exit(fn -> -        Pleroma.Config.get(@oauth_config_path, oauth_token_config) -      end) -    end +    clear_config([:oauth2, :issue_new_refresh_token])      test "issues a new access token with keep fresh token" do -      Pleroma.Config.put(@oauth_config_path, true) +      Pleroma.Config.put([:oauth2, :issue_new_refresh_token], true)        user = insert(:user)        app = insert(:oauth_app, scopes: ["read", "write"]) @@ -908,7 +986,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do      end      test "issues a new access token with new fresh token" do -      Pleroma.Config.put(@oauth_config_path, false) +      Pleroma.Config.put([:oauth2, :issue_new_refresh_token], false)        user = insert(:user)        app = insert(:oauth_app, scopes: ["read", "write"]) diff --git a/test/web/oauth/token/utils_test.exs b/test/web/oauth/token/utils_test.exs index 20e338cab..dc1f9a986 100644 --- a/test/web/oauth/token/utils_test.exs +++ b/test/web/oauth/token/utils_test.exs @@ -1,5 +1,5 @@  # Pleroma: A lightweight social networking server -# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.Web.OAuth.Token.UtilsTest do diff --git a/test/web/oauth/token_test.exs b/test/web/oauth/token_test.exs index 3c07309b7..5359940f8 100644 --- a/test/web/oauth/token_test.exs +++ b/test/web/oauth/token_test.exs @@ -1,5 +1,5 @@  # Pleroma: A lightweight social networking server -# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.Web.OAuth.TokenTest do diff --git a/test/web/ostatus/activity_representer_test.exs b/test/web/ostatus/activity_representer_test.exs deleted file mode 100644 index a3a92ce5b..000000000 --- a/test/web/ostatus/activity_representer_test.exs +++ /dev/null @@ -1,300 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/> -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Web.OStatus.ActivityRepresenterTest do -  use Pleroma.DataCase - -  alias Pleroma.Activity -  alias Pleroma.Object -  alias Pleroma.User -  alias Pleroma.Web.ActivityPub.ActivityPub -  alias Pleroma.Web.OStatus -  alias Pleroma.Web.OStatus.ActivityRepresenter - -  import Pleroma.Factory -  import Tesla.Mock - -  setup do -    mock(fn env -> apply(HttpRequestMock, :request, [env]) end) -    :ok -  end - -  test "an external note activity" do -    incoming = File.read!("test/fixtures/mastodon-note-cw.xml") -    {:ok, [activity]} = OStatus.handle_incoming(incoming) - -    user = User.get_cached_by_ap_id(activity.data["actor"]) - -    tuple = ActivityRepresenter.to_simple_form(activity, user) - -    res = :xmerl.export_simple_content(tuple, :xmerl_xml) |> IO.iodata_to_binary() - -    assert String.contains?( -             res, -             ~s{<link type="text/html" href="https://mastodon.social/users/lambadalambda/updates/2314748" rel="alternate"/>} -           ) -  end - -  test "a note activity" do -    note_activity = insert(:note_activity) -    object_data = Object.normalize(note_activity).data - -    user = User.get_cached_by_ap_id(note_activity.data["actor"]) - -    expected = """ -    <activity:object-type>http://activitystrea.ms/schema/1.0/note</activity:object-type> -    <activity:verb>http://activitystrea.ms/schema/1.0/post</activity:verb> -    <id>#{object_data["id"]}</id> -    <title>New note by #{user.nickname}</title> -    <content type="html">#{object_data["content"]}</content> -    <published>#{object_data["published"]}</published> -    <updated>#{object_data["published"]}</updated> -    <ostatus:conversation ref="#{note_activity.data["context"]}">#{note_activity.data["context"]}</ostatus:conversation> -    <link ref="#{note_activity.data["context"]}" rel="ostatus:conversation" /> -    <summary>#{object_data["summary"]}</summary> -    <link type="application/atom+xml" href="#{object_data["id"]}" rel="self" /> -    <link type="text/html" href="#{object_data["id"]}" rel="alternate" /> -    <category term="2hu"/> -    <link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/collection" href="http://activityschema.org/collection/public"/> -    <link name="2hu" rel="emoji" href="corndog.png" /> -    """ - -    tuple = ActivityRepresenter.to_simple_form(note_activity, user) - -    res = :xmerl.export_simple_content(tuple, :xmerl_xml) |> IO.iodata_to_binary() - -    assert clean(res) == clean(expected) -  end - -  test "a reply note" do -    user = insert(:user) -    note_object = insert(:note) -    _note = insert(:note_activity, %{note: note_object}) -    object = insert(:note, %{data: %{"inReplyTo" => note_object.data["id"]}}) -    answer = insert(:note_activity, %{note: object}) - -    Repo.update!( -      Object.change(note_object, %{data: Map.put(note_object.data, "external_url", "someurl")}) -    ) - -    expected = """ -    <activity:object-type>http://activitystrea.ms/schema/1.0/note</activity:object-type> -    <activity:verb>http://activitystrea.ms/schema/1.0/post</activity:verb> -    <id>#{object.data["id"]}</id> -    <title>New note by #{user.nickname}</title> -    <content type="html">#{object.data["content"]}</content> -    <published>#{object.data["published"]}</published> -    <updated>#{object.data["published"]}</updated> -    <ostatus:conversation ref="#{answer.data["context"]}">#{answer.data["context"]}</ostatus:conversation> -    <link ref="#{answer.data["context"]}" rel="ostatus:conversation" /> -    <summary>2hu</summary> -    <link type="application/atom+xml" href="#{object.data["id"]}" rel="self" /> -    <link type="text/html" href="#{object.data["id"]}" rel="alternate" /> -    <category term="2hu"/> -    <thr:in-reply-to ref="#{note_object.data["id"]}" href="someurl" /> -    <link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/collection" href="http://activityschema.org/collection/public"/> -    <link name="2hu" rel="emoji" href="corndog.png" /> -    """ - -    tuple = ActivityRepresenter.to_simple_form(answer, user) - -    res = :xmerl.export_simple_content(tuple, :xmerl_xml) |> IO.iodata_to_binary() - -    assert clean(res) == clean(expected) -  end - -  test "an announce activity" do -    note = insert(:note_activity) -    user = insert(:user) -    object = Object.normalize(note) - -    {:ok, announce, _object} = ActivityPub.announce(user, object) - -    announce = Activity.get_by_id(announce.id) - -    note_user = User.get_cached_by_ap_id(note.data["actor"]) -    note = Activity.get_by_id(note.id) - -    note_xml = -      ActivityRepresenter.to_simple_form(note, note_user, true) -      |> :xmerl.export_simple_content(:xmerl_xml) -      |> to_string - -    expected = """ -    <activity:object-type>http://activitystrea.ms/schema/1.0/activity</activity:object-type> -    <activity:verb>http://activitystrea.ms/schema/1.0/share</activity:verb> -    <id>#{announce.data["id"]}</id> -    <title>#{user.nickname} repeated a notice</title> -    <content type="html">RT #{object.data["content"]}</content> -    <published>#{announce.data["published"]}</published> -    <updated>#{announce.data["published"]}</updated> -    <ostatus:conversation ref="#{announce.data["context"]}">#{announce.data["context"]}</ostatus:conversation> -    <link ref="#{announce.data["context"]}" rel="ostatus:conversation" /> -    <link rel="self" type="application/atom+xml" href="#{announce.data["id"]}"/> -    <activity:object> -      #{note_xml} -    </activity:object> -    <link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/person" href="#{ -      note.data["actor"] -    }"/> -    <link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/collection" href="http://activityschema.org/collection/public"/> -    """ - -    announce_xml = -      ActivityRepresenter.to_simple_form(announce, user) -      |> :xmerl.export_simple_content(:xmerl_xml) -      |> to_string - -    assert clean(expected) == clean(announce_xml) -  end - -  test "a like activity" do -    note = insert(:note) -    user = insert(:user) -    {:ok, like, _note} = ActivityPub.like(user, note) - -    tuple = ActivityRepresenter.to_simple_form(like, user) -    refute is_nil(tuple) - -    res = :xmerl.export_simple_content(tuple, :xmerl_xml) |> IO.iodata_to_binary() - -    expected = """ -    <activity:verb>http://activitystrea.ms/schema/1.0/favorite</activity:verb> -    <id>#{like.data["id"]}</id> -    <title>New favorite by #{user.nickname}</title> -    <content type="html">#{user.nickname} favorited something</content> -    <published>#{like.data["published"]}</published> -    <updated>#{like.data["published"]}</updated> -    <activity:object> -      <activity:object-type>http://activitystrea.ms/schema/1.0/note</activity:object-type> -      <id>#{note.data["id"]}</id> -    </activity:object> -    <ostatus:conversation ref="#{like.data["context"]}">#{like.data["context"]}</ostatus:conversation> -    <link ref="#{like.data["context"]}" rel="ostatus:conversation" /> -    <link rel="self" type="application/atom+xml" href="#{like.data["id"]}"/> -    <thr:in-reply-to ref="#{note.data["id"]}" /> -    <link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/person" href="#{ -      note.data["actor"] -    }"/> -    <link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/collection" href="http://activityschema.org/collection/public"/> -    """ - -    assert clean(res) == clean(expected) -  end - -  test "a follow activity" do -    follower = insert(:user) -    followed = insert(:user) - -    {:ok, activity} = -      ActivityPub.insert(%{ -        "type" => "Follow", -        "actor" => follower.ap_id, -        "object" => followed.ap_id, -        "to" => [followed.ap_id] -      }) - -    tuple = ActivityRepresenter.to_simple_form(activity, follower) - -    refute is_nil(tuple) - -    res = :xmerl.export_simple_content(tuple, :xmerl_xml) |> IO.iodata_to_binary() - -    expected = """ -    <activity:object-type>http://activitystrea.ms/schema/1.0/activity</activity:object-type> -    <activity:verb>http://activitystrea.ms/schema/1.0/follow</activity:verb> -    <id>#{activity.data["id"]}</id> -    <title>#{follower.nickname} started following #{activity.data["object"]}</title> -    <content type="html"> #{follower.nickname} started following #{activity.data["object"]}</content> -    <published>#{activity.data["published"]}</published> -    <updated>#{activity.data["published"]}</updated> -    <activity:object> -      <activity:object-type>http://activitystrea.ms/schema/1.0/person</activity:object-type> -      <id>#{activity.data["object"]}</id> -      <uri>#{activity.data["object"]}</uri> -    </activity:object> -    <link rel="self" type="application/atom+xml" href="#{activity.data["id"]}"/> -    <link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/person" href="#{ -      activity.data["object"] -    }"/> -    """ - -    assert clean(res) == clean(expected) -  end - -  test "an unfollow activity" do -    follower = insert(:user) -    followed = insert(:user) -    {:ok, _activity} = ActivityPub.follow(follower, followed) -    {:ok, activity} = ActivityPub.unfollow(follower, followed) - -    tuple = ActivityRepresenter.to_simple_form(activity, follower) - -    refute is_nil(tuple) - -    res = :xmerl.export_simple_content(tuple, :xmerl_xml) |> IO.iodata_to_binary() - -    expected = """ -    <activity:object-type>http://activitystrea.ms/schema/1.0/activity</activity:object-type> -    <activity:verb>http://activitystrea.ms/schema/1.0/unfollow</activity:verb> -    <id>#{activity.data["id"]}</id> -    <title>#{follower.nickname} stopped following #{followed.ap_id}</title> -    <content type="html"> #{follower.nickname} stopped following #{followed.ap_id}</content> -    <published>#{activity.data["published"]}</published> -    <updated>#{activity.data["published"]}</updated> -    <activity:object> -      <activity:object-type>http://activitystrea.ms/schema/1.0/person</activity:object-type> -      <id>#{followed.ap_id}</id> -      <uri>#{followed.ap_id}</uri> -    </activity:object> -    <link rel="self" type="application/atom+xml" href="#{activity.data["id"]}"/> -    <link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/person" href="#{ -      followed.ap_id -    }"/> -    """ - -    assert clean(res) == clean(expected) -  end - -  test "a delete" do -    user = insert(:user) - -    activity = %Activity{ -      data: %{ -        "id" => "ap_id", -        "type" => "Delete", -        "actor" => user.ap_id, -        "object" => "some_id", -        "published" => "2017-06-18T12:00:18+00:00" -      } -    } - -    tuple = ActivityRepresenter.to_simple_form(activity, nil) - -    refute is_nil(tuple) - -    res = :xmerl.export_simple_content(tuple, :xmerl_xml) |> IO.iodata_to_binary() - -    expected = """ -    <activity:object-type>http://activitystrea.ms/schema/1.0/activity</activity:object-type> -    <activity:verb>http://activitystrea.ms/schema/1.0/delete</activity:verb> -    <id>#{activity.data["object"]}</id> -    <title>An object was deleted</title> -    <content type="html">An object was deleted</content> -    <published>#{activity.data["published"]}</published> -    <updated>#{activity.data["published"]}</updated> -    """ - -    assert clean(res) == clean(expected) -  end - -  test "an unknown activity" do -    tuple = ActivityRepresenter.to_simple_form(%Activity{}, nil) -    assert is_nil(tuple) -  end - -  defp clean(string) do -    String.replace(string, ~r/\s/, "") -  end -end diff --git a/test/web/ostatus/feed_representer_test.exs b/test/web/ostatus/feed_representer_test.exs deleted file mode 100644 index 3c7b126e7..000000000 --- a/test/web/ostatus/feed_representer_test.exs +++ /dev/null @@ -1,59 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/> -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Web.OStatus.FeedRepresenterTest do -  use Pleroma.DataCase -  import Pleroma.Factory -  alias Pleroma.User -  alias Pleroma.Web.OStatus -  alias Pleroma.Web.OStatus.ActivityRepresenter -  alias Pleroma.Web.OStatus.FeedRepresenter -  alias Pleroma.Web.OStatus.UserRepresenter - -  test "returns a feed of the last 20 items of the user" do -    note_activity = insert(:note_activity) -    user = User.get_cached_by_ap_id(note_activity.data["actor"]) - -    tuple = FeedRepresenter.to_simple_form(user, [note_activity], [user]) - -    most_recent_update = -      note_activity.updated_at -      |> NaiveDateTime.to_iso8601() - -    res = :xmerl.export_simple_content(tuple, :xmerl_xml) |> to_string - -    user_xml = -      UserRepresenter.to_simple_form(user) -      |> :xmerl.export_simple_content(:xmerl_xml) - -    entry_xml = -      ActivityRepresenter.to_simple_form(note_activity, user) -      |> :xmerl.export_simple_content(:xmerl_xml) - -    expected = """ -    <feed xmlns="http://www.w3.org/2005/Atom" xmlns:thr="http://purl.org/syndication/thread/1.0" xmlns:activity="http://activitystrea.ms/spec/1.0/" xmlns:poco="http://portablecontacts.net/spec/1.0" xmlns:ostatus="http://ostatus.org/schema/1.0"> -      <id>#{OStatus.feed_path(user)}</id> -      <title>#{user.nickname}'s timeline</title> -      <updated>#{most_recent_update}</updated> -      <logo>#{User.avatar_url(user)}</logo> -      <link rel="hub" href="#{OStatus.pubsub_path(user)}" /> -      <link rel="salmon" href="#{OStatus.salmon_path(user)}" /> -      <link rel="self" href="#{OStatus.feed_path(user)}" type="application/atom+xml" /> -      <author> -        #{user_xml} -      </author> -      <link rel="next" href="#{OStatus.feed_path(user)}?max_id=#{note_activity.id}" type="application/atom+xml" /> -      <entry> -        #{entry_xml} -      </entry> -    </feed> -    """ - -    assert clean(res) == clean(expected) -  end - -  defp clean(string) do -    String.replace(string, ~r/\s/, "") -  end -end diff --git a/test/web/ostatus/incoming_documents/delete_handling_test.exs b/test/web/ostatus/incoming_documents/delete_handling_test.exs deleted file mode 100644 index cd0447af7..000000000 --- a/test/web/ostatus/incoming_documents/delete_handling_test.exs +++ /dev/null @@ -1,48 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Web.OStatus.DeleteHandlingTest do -  use Pleroma.DataCase - -  import Pleroma.Factory -  import Tesla.Mock - -  alias Pleroma.Activity -  alias Pleroma.Object -  alias Pleroma.Web.OStatus - -  setup do -    mock(fn env -> apply(HttpRequestMock, :request, [env]) end) -    :ok -  end - -  describe "deletions" do -    test "it removes the mentioned activity" do -      note = insert(:note_activity) -      second_note = insert(:note_activity) -      object = Object.normalize(note) -      second_object = Object.normalize(second_note) -      user = insert(:user) - -      {:ok, like, _object} = Pleroma.Web.ActivityPub.ActivityPub.like(user, object) - -      incoming = -        File.read!("test/fixtures/delete.xml") -        |> String.replace( -          "tag:mastodon.sdf.org,2017-06-10:objectId=310513:objectType=Status", -          object.data["id"] -        ) - -      {:ok, [delete]} = OStatus.handle_incoming(incoming) - -      refute Activity.get_by_id(note.id) -      refute Activity.get_by_id(like.id) -      assert Object.get_by_ap_id(object.data["id"]).data["type"] == "Tombstone" -      assert Activity.get_by_id(second_note.id) -      assert Object.get_by_ap_id(second_object.data["id"]) - -      assert delete.data["type"] == "Delete" -    end -  end -end diff --git a/test/web/ostatus/ostatus_controller_test.exs b/test/web/ostatus/ostatus_controller_test.exs index bb7648bdd..50235dfef 100644 --- a/test/web/ostatus/ostatus_controller_test.exs +++ b/test/web/ostatus/ostatus_controller_test.exs @@ -1,260 +1,280 @@  # Pleroma: A lightweight social networking server -# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.Web.OStatus.OStatusControllerTest do    use Pleroma.Web.ConnCase -  import ExUnit.CaptureLog    import Pleroma.Factory    alias Pleroma.Object    alias Pleroma.User    alias Pleroma.Web.CommonAPI -  alias Pleroma.Web.OStatus.ActivityRepresenter    setup_all do      Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end) - -    config_path = [:instance, :federating] -    initial_setting = Pleroma.Config.get(config_path) - -    Pleroma.Config.put(config_path, true) -    on_exit(fn -> Pleroma.Config.put(config_path, initial_setting) end) -      :ok    end -  describe "salmon_incoming" do -    test "decodes a salmon", %{conn: conn} do -      user = insert(:user) -      salmon = File.read!("test/fixtures/salmon.xml") +  clear_config_all([:instance, :federating]) do +    Pleroma.Config.put([:instance, :federating], true) +  end -      assert capture_log(fn -> -               conn = -                 conn -                 |> put_req_header("content-type", "application/atom+xml") -                 |> post("/users/#{user.nickname}/salmon", salmon) +  describe "GET object/2" do +    test "redirects to /notice/id for html format", %{conn: conn} do +      note_activity = insert(:note_activity) +      object = Object.normalize(note_activity) +      [_, uuid] = hd(Regex.scan(~r/.+\/([\w-]+)$/, object.data["id"])) +      url = "/objects/#{uuid}" -               assert response(conn, 200) -             end) =~ "[error]" -    end +      conn = +        conn +        |> put_req_header("accept", "text/html") +        |> get(url) -    test "decodes a salmon with a changed magic key", %{conn: conn} do -      user = insert(:user) -      salmon = File.read!("test/fixtures/salmon.xml") - -      assert capture_log(fn -> -               conn = -                 conn -                 |> put_req_header("content-type", "application/atom+xml") -                 |> post("/users/#{user.nickname}/salmon", salmon) - -               assert response(conn, 200) -             end) =~ "[error]" - -      # Set a wrong magic-key for a user so it has to refetch -      salmon_user = User.get_cached_by_ap_id("http://gs.example.org:4040/index.php/user/1") - -      # Wrong key -      info_cng = -        User.Info.remote_user_creation(salmon_user.info, %{ -          magic_key: -            "RSA.pu0s-halox4tu7wmES1FVSx6u-4wc0YrUFXcqWXZG4-27UmbCOpMQftRCldNRfyA-qLbz-eqiwrong1EwUvjsD4cYbAHNGHwTvDOyx5AKthQUP44ykPv7kjKGh3DWKySJvcs9tlUG87hlo7AvnMo9pwRS_Zz2CacQ-MKaXyDepk=.AQAB" -        }) - -      salmon_user -      |> Ecto.Changeset.change() -      |> Ecto.Changeset.put_embed(:info, info_cng) -      |> User.update_and_set_cache() - -      assert capture_log(fn -> -               conn = -                 build_conn() -                 |> put_req_header("content-type", "application/atom+xml") -                 |> post("/users/#{user.nickname}/salmon", salmon) - -               assert response(conn, 200) -             end) =~ "[error]" +      assert redirected_to(conn) == "/notice/#{note_activity.id}"      end -  end -  test "gets a feed", %{conn: conn} do -    note_activity = insert(:note_activity) -    object = Object.normalize(note_activity) -    user = User.get_cached_by_ap_id(note_activity.data["actor"]) +    test "404s on private objects", %{conn: conn} do +      note_activity = insert(:direct_note_activity) +      object = Object.normalize(note_activity) +      [_, uuid] = hd(Regex.scan(~r/.+\/([\w-]+)$/, object.data["id"])) -    conn =        conn -      |> put_req_header("content-type", "application/atom+xml") -      |> get("/users/#{user.nickname}/feed.atom") +      |> get("/objects/#{uuid}") +      |> response(404) +    end -    assert response(conn, 200) =~ object.data["content"] +    test "404s on nonexisting objects", %{conn: conn} do +      conn +      |> get("/objects/123") +      |> response(404) +    end    end -  test "returns 404 for a missing feed", %{conn: conn} do -    conn = -      conn -      |> put_req_header("content-type", "application/atom+xml") -      |> get("/users/nonexisting/feed.atom") +  describe "GET activity/2" do +    test "redirects to /notice/id for html format", %{conn: conn} do +      note_activity = insert(:note_activity) +      [_, uuid] = hd(Regex.scan(~r/.+\/([\w-]+)$/, note_activity.data["id"])) -    assert response(conn, 404) -  end +      conn = +        conn +        |> put_req_header("accept", "text/html") +        |> get("/activities/#{uuid}") -  test "gets an object", %{conn: conn} do -    note_activity = insert(:note_activity) -    object = Object.normalize(note_activity) -    user = User.get_cached_by_ap_id(note_activity.data["actor"]) -    [_, uuid] = hd(Regex.scan(~r/.+\/([\w-]+)$/, object.data["id"])) -    url = "/objects/#{uuid}" +      assert redirected_to(conn) == "/notice/#{note_activity.id}" +    end + +    test "404s on private activities", %{conn: conn} do +      note_activity = insert(:direct_note_activity) +      [_, uuid] = hd(Regex.scan(~r/.+\/([\w-]+)$/, note_activity.data["id"])) -    conn =        conn -      |> put_req_header("accept", "application/xml") -      |> get(url) +      |> get("/activities/#{uuid}") +      |> response(404) +    end -    expected = -      ActivityRepresenter.to_simple_form(note_activity, user, true) -      |> ActivityRepresenter.wrap_with_entry() -      |> :xmerl.export_simple(:xmerl_xml) -      |> to_string +    test "404s on nonexistent activities", %{conn: conn} do +      conn +      |> get("/activities/123") +      |> response(404) +    end -    assert response(conn, 200) == expected -  end +    test "gets an activity in AS2 format", %{conn: conn} do +      note_activity = insert(:note_activity) +      [_, uuid] = hd(Regex.scan(~r/.+\/([\w-]+)$/, note_activity.data["id"])) +      url = "/activities/#{uuid}" -  test "404s on private objects", %{conn: conn} do -    note_activity = insert(:direct_note_activity) -    object = Object.normalize(note_activity) -    [_, uuid] = hd(Regex.scan(~r/.+\/([\w-]+)$/, object.data["id"])) +      conn = +        conn +        |> put_req_header("accept", "application/activity+json") +        |> get(url) -    conn -    |> get("/objects/#{uuid}") -    |> response(404) +      assert json_response(conn, 200) +    end    end -  test "404s on nonexisting objects", %{conn: conn} do -    conn -    |> get("/objects/123") -    |> response(404) -  end +  describe "GET notice/2" do +    test "redirects to a proper object URL when json requested and the object is local", %{ +      conn: conn +    } do +      note_activity = insert(:note_activity) +      expected_redirect_url = Object.normalize(note_activity).data["id"] -  test "gets an activity in xml format", %{conn: conn} do -    note_activity = insert(:note_activity) -    [_, uuid] = hd(Regex.scan(~r/.+\/([\w-]+)$/, note_activity.data["id"])) +      redirect_url = +        conn +        |> put_req_header("accept", "application/activity+json") +        |> get("/notice/#{note_activity.id}") +        |> redirected_to() -    conn -    |> put_req_header("accept", "application/xml") -    |> get("/activities/#{uuid}") -    |> response(200) -  end +      assert redirect_url == expected_redirect_url +    end -  test "404s on deleted objects", %{conn: conn} do -    note_activity = insert(:note_activity) -    object = Object.normalize(note_activity) -    [_, uuid] = hd(Regex.scan(~r/.+\/([\w-]+)$/, object.data["id"])) +    test "returns a 404 on remote notice when json requested", %{conn: conn} do +      note_activity = insert(:note_activity, local: false) -    conn -    |> put_req_header("accept", "application/xml") -    |> get("/objects/#{uuid}") -    |> response(200) +      conn +      |> put_req_header("accept", "application/activity+json") +      |> get("/notice/#{note_activity.id}") +      |> response(404) +    end -    Object.delete(object) +    test "500s when actor not found", %{conn: conn} do +      note_activity = insert(:note_activity) +      user = User.get_cached_by_ap_id(note_activity.data["actor"]) +      User.invalidate_cache(user) +      Pleroma.Repo.delete(user) -    conn -    |> put_req_header("accept", "application/xml") -    |> get("/objects/#{uuid}") -    |> response(404) -  end +      conn = +        conn +        |> get("/notice/#{note_activity.id}") -  test "404s on private activities", %{conn: conn} do -    note_activity = insert(:direct_note_activity) -    [_, uuid] = hd(Regex.scan(~r/.+\/([\w-]+)$/, note_activity.data["id"])) +      assert response(conn, 500) == ~S({"error":"Something went wrong"}) +    end -    conn -    |> get("/activities/#{uuid}") -    |> response(404) -  end +    test "render html for redirect for html format", %{conn: conn} do +      note_activity = insert(:note_activity) -  test "404s on nonexistent activities", %{conn: conn} do -    conn -    |> get("/activities/123") -    |> response(404) -  end +      resp = +        conn +        |> put_req_header("accept", "text/html") +        |> get("/notice/#{note_activity.id}") +        |> response(200) -  test "gets a notice in xml format", %{conn: conn} do -    note_activity = insert(:note_activity) +      assert resp =~ +               "<meta content=\"#{Pleroma.Web.base_url()}/notice/#{note_activity.id}\" property=\"og:url\">" -    conn -    |> get("/notice/#{note_activity.id}") -    |> response(200) -  end +      user = insert(:user) -  test "gets a notice in AS2 format", %{conn: conn} do -    note_activity = insert(:note_activity) +      {:ok, like_activity, _} = CommonAPI.favorite(note_activity.id, user) -    conn -    |> put_req_header("accept", "application/activity+json") -    |> get("/notice/#{note_activity.id}") -    |> json_response(200) -  end +      assert like_activity.data["type"] == "Like" -  test "only gets a notice in AS2 format for Create messages", %{conn: conn} do -    note_activity = insert(:note_activity) -    url = "/notice/#{note_activity.id}" +      resp = +        conn +        |> put_req_header("accept", "text/html") +        |> get("/notice/#{like_activity.id}") +        |> response(200) -    conn = -      conn -      |> put_req_header("accept", "application/activity+json") -      |> get(url) +      assert resp =~ "<!--server-generated-meta-->" +    end -    assert json_response(conn, 200) +    test "404s a private notice", %{conn: conn} do +      note_activity = insert(:direct_note_activity) +      url = "/notice/#{note_activity.id}" -    user = insert(:user) +      conn = +        conn +        |> get(url) -    {:ok, like_activity, _} = CommonAPI.favorite(note_activity.id, user) -    url = "/notice/#{like_activity.id}" +      assert response(conn, 404) +    end -    assert like_activity.data["type"] == "Like" +    test "404s a nonexisting notice", %{conn: conn} do +      url = "/notice/123" -    conn = -      build_conn() -      |> put_req_header("accept", "application/activity+json") -      |> get(url) +      conn = +        conn +        |> get(url) -    assert response(conn, 404) +      assert response(conn, 404) +    end    end -  test "gets an activity in AS2 format", %{conn: conn} do -    note_activity = insert(:note_activity) -    [_, uuid] = hd(Regex.scan(~r/.+\/([\w-]+)$/, note_activity.data["id"])) -    url = "/activities/#{uuid}" +  describe "GET /notice/:id/embed_player" do +    test "render embed player", %{conn: conn} do +      note_activity = insert(:note_activity) +      object = Pleroma.Object.normalize(note_activity) + +      object_data = +        Map.put(object.data, "attachment", [ +          %{ +            "url" => [ +              %{ +                "href" => +                  "https://peertube.moe/static/webseed/df5f464b-be8d-46fb-ad81-2d4c2d1630e3-480.mp4", +                "mediaType" => "video/mp4", +                "type" => "Link" +              } +            ] +          } +        ]) + +      object +      |> Ecto.Changeset.change(data: object_data) +      |> Pleroma.Repo.update() + +      conn = +        conn +        |> get("/notice/#{note_activity.id}/embed_player") + +      assert Plug.Conn.get_resp_header(conn, "x-frame-options") == ["ALLOW"] + +      assert Plug.Conn.get_resp_header( +               conn, +               "content-security-policy" +             ) == [ +               "default-src 'none';style-src 'self' 'unsafe-inline';img-src 'self' data: https:; media-src 'self' https:;" +             ] + +      assert response(conn, 200) =~ +               "<video controls loop><source src=\"https://peertube.moe/static/webseed/df5f464b-be8d-46fb-ad81-2d4c2d1630e3-480.mp4\" type=\"video/mp4\">Your browser does not support video/mp4 playback.</video>" +    end -    conn = -      conn -      |> put_req_header("accept", "application/activity+json") -      |> get(url) +    test "404s when activity isn't create", %{conn: conn} do +      note_activity = insert(:note_activity, data_attrs: %{"type" => "Like"}) -    assert json_response(conn, 200) -  end +      assert conn +             |> get("/notice/#{note_activity.id}/embed_player") +             |> response(404) +    end -  test "404s a private notice", %{conn: conn} do -    note_activity = insert(:direct_note_activity) -    url = "/notice/#{note_activity.id}" +    test "404s when activity is direct message", %{conn: conn} do +      note_activity = insert(:note_activity, data_attrs: %{"directMessage" => true}) -    conn = -      conn -      |> get(url) +      assert conn +             |> get("/notice/#{note_activity.id}/embed_player") +             |> response(404) +    end -    assert response(conn, 404) -  end +    test "404s when attachment is empty", %{conn: conn} do +      note_activity = insert(:note_activity) +      object = Pleroma.Object.normalize(note_activity) +      object_data = Map.put(object.data, "attachment", []) -  test "404s a nonexisting notice", %{conn: conn} do -    url = "/notice/123" +      object +      |> Ecto.Changeset.change(data: object_data) +      |> Pleroma.Repo.update() -    conn = -      conn -      |> get(url) +      assert conn +             |> get("/notice/#{note_activity.id}/embed_player") +             |> response(404) +    end -    assert response(conn, 404) +    test "404s when attachment isn't audio or video", %{conn: conn} do +      note_activity = insert(:note_activity) +      object = Pleroma.Object.normalize(note_activity) + +      object_data = +        Map.put(object.data, "attachment", [ +          %{ +            "url" => [ +              %{ +                "href" => "https://peertube.moe/static/webseed/480.jpg", +                "mediaType" => "image/jpg", +                "type" => "Link" +              } +            ] +          } +        ]) + +      object +      |> Ecto.Changeset.change(data: object_data) +      |> Pleroma.Repo.update() + +      assert conn +             |> get("/notice/#{note_activity.id}/embed_player") +             |> response(404) +    end    end  end diff --git a/test/web/ostatus/ostatus_test.exs b/test/web/ostatus/ostatus_test.exs deleted file mode 100644 index 4e8f3a0fc..000000000 --- a/test/web/ostatus/ostatus_test.exs +++ /dev/null @@ -1,581 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/> -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Web.OStatusTest do -  use Pleroma.DataCase -  alias Pleroma.Activity -  alias Pleroma.Instances -  alias Pleroma.Object -  alias Pleroma.Repo -  alias Pleroma.User -  alias Pleroma.Web.OStatus -  alias Pleroma.Web.XML - -  import ExUnit.CaptureLog -  import Mock -  import Pleroma.Factory - -  setup_all do -    Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end) -    :ok -  end - -  test "don't insert create notes twice" do -    incoming = File.read!("test/fixtures/incoming_note_activity.xml") -    {:ok, [activity]} = OStatus.handle_incoming(incoming) -    assert {:ok, [activity]} == OStatus.handle_incoming(incoming) -  end - -  test "handle incoming note - GS, Salmon" do -    incoming = File.read!("test/fixtures/incoming_note_activity.xml") -    {:ok, [activity]} = OStatus.handle_incoming(incoming) -    object = Object.normalize(activity) - -    user = User.get_cached_by_ap_id(activity.data["actor"]) -    assert user.info.note_count == 1 -    assert activity.data["type"] == "Create" -    assert object.data["type"] == "Note" - -    assert object.data["id"] == "tag:gs.example.org:4040,2017-04-23:noticeId=29:objectType=note" - -    assert activity.data["published"] == "2017-04-23T14:51:03+00:00" -    assert object.data["published"] == "2017-04-23T14:51:03+00:00" - -    assert activity.data["context"] == -             "tag:gs.example.org:4040,2017-04-23:objectType=thread:nonce=f09e22f58abd5c7b" - -    assert "http://pleroma.example.org:4000/users/lain3" in activity.data["to"] -    assert object.data["emoji"] == %{"marko" => "marko.png", "reimu" => "reimu.png"} -    assert activity.local == false -  end - -  test "handle incoming notes - GS, subscription" do -    incoming = File.read!("test/fixtures/ostatus_incoming_post.xml") -    {:ok, [activity]} = OStatus.handle_incoming(incoming) -    object = Object.normalize(activity) - -    assert activity.data["type"] == "Create" -    assert object.data["type"] == "Note" -    assert object.data["actor"] == "https://social.heldscal.la/user/23211" -    assert object.data["content"] == "Will it blend?" -    user = User.get_cached_by_ap_id(activity.data["actor"]) -    assert User.ap_followers(user) in activity.data["to"] -    assert "https://www.w3.org/ns/activitystreams#Public" in activity.data["to"] -  end - -  test "handle incoming notes with attachments - GS, subscription" do -    incoming = File.read!("test/fixtures/incoming_websub_gnusocial_attachments.xml") -    {:ok, [activity]} = OStatus.handle_incoming(incoming) -    object = Object.normalize(activity) - -    assert activity.data["type"] == "Create" -    assert object.data["type"] == "Note" -    assert object.data["actor"] == "https://social.heldscal.la/user/23211" -    assert object.data["attachment"] |> length == 2 -    assert object.data["external_url"] == "https://social.heldscal.la/notice/2020923" -    assert "https://www.w3.org/ns/activitystreams#Public" in activity.data["to"] -  end - -  test "handle incoming notes with tags" do -    incoming = File.read!("test/fixtures/ostatus_incoming_post_tag.xml") -    {:ok, [activity]} = OStatus.handle_incoming(incoming) -    object = Object.normalize(activity) - -    assert object.data["tag"] == ["nsfw"] -    assert "https://www.w3.org/ns/activitystreams#Public" in activity.data["to"] -  end - -  test "handle incoming notes - Mastodon, salmon, reply" do -    # It uses the context of the replied to object -    Repo.insert!(%Object{ -      data: %{ -        "id" => "https://pleroma.soykaf.com/objects/c237d966-ac75-4fe3-a87a-d89d71a3a7a4", -        "context" => "2hu" -      } -    }) - -    incoming = File.read!("test/fixtures/incoming_reply_mastodon.xml") -    {:ok, [activity]} = OStatus.handle_incoming(incoming) -    object = Object.normalize(activity) - -    assert activity.data["type"] == "Create" -    assert object.data["type"] == "Note" -    assert object.data["actor"] == "https://mastodon.social/users/lambadalambda" -    assert activity.data["context"] == "2hu" -    assert "https://www.w3.org/ns/activitystreams#Public" in activity.data["to"] -  end - -  test "handle incoming notes - Mastodon, with CW" do -    incoming = File.read!("test/fixtures/mastodon-note-cw.xml") -    {:ok, [activity]} = OStatus.handle_incoming(incoming) -    object = Object.normalize(activity) - -    assert activity.data["type"] == "Create" -    assert object.data["type"] == "Note" -    assert object.data["actor"] == "https://mastodon.social/users/lambadalambda" -    assert object.data["summary"] == "technologic" -    assert "https://www.w3.org/ns/activitystreams#Public" in activity.data["to"] -  end - -  test "handle incoming unlisted messages, put public into cc" do -    incoming = File.read!("test/fixtures/mastodon-note-unlisted.xml") -    {:ok, [activity]} = OStatus.handle_incoming(incoming) -    object = Object.normalize(activity) - -    refute "https://www.w3.org/ns/activitystreams#Public" in activity.data["to"] -    assert "https://www.w3.org/ns/activitystreams#Public" in activity.data["cc"] -    refute "https://www.w3.org/ns/activitystreams#Public" in object.data["to"] -    assert "https://www.w3.org/ns/activitystreams#Public" in object.data["cc"] -  end - -  test "handle incoming retweets - Mastodon, with CW" do -    incoming = File.read!("test/fixtures/cw_retweet.xml") -    {:ok, [[_activity, retweeted_activity]]} = OStatus.handle_incoming(incoming) -    retweeted_object = Object.normalize(retweeted_activity) - -    assert retweeted_object.data["summary"] == "Hey." -  end - -  test "handle incoming notes - GS, subscription, reply" do -    incoming = File.read!("test/fixtures/ostatus_incoming_reply.xml") -    {:ok, [activity]} = OStatus.handle_incoming(incoming) -    object = Object.normalize(activity) - -    assert activity.data["type"] == "Create" -    assert object.data["type"] == "Note" -    assert object.data["actor"] == "https://social.heldscal.la/user/23211" - -    assert object.data["content"] == -             "@<a href=\"https://gs.archae.me/user/4687\" class=\"h-card u-url p-nickname mention\" title=\"shpbot\">shpbot</a> why not indeed." - -    assert object.data["inReplyTo"] == -             "tag:gs.archae.me,2017-04-30:noticeId=778260:objectType=note" - -    assert "https://www.w3.org/ns/activitystreams#Public" in activity.data["to"] -  end - -  test "handle incoming retweets - GS, subscription" do -    incoming = File.read!("test/fixtures/share-gs.xml") -    {:ok, [[activity, retweeted_activity]]} = OStatus.handle_incoming(incoming) - -    assert activity.data["type"] == "Announce" -    assert activity.data["actor"] == "https://social.heldscal.la/user/23211" -    assert activity.data["object"] == retweeted_activity.data["object"] -    assert "https://pleroma.soykaf.com/users/lain" in activity.data["to"] -    refute activity.local - -    retweeted_activity = Activity.get_by_id(retweeted_activity.id) -    retweeted_object = Object.normalize(retweeted_activity) -    assert retweeted_activity.data["type"] == "Create" -    assert retweeted_activity.data["actor"] == "https://pleroma.soykaf.com/users/lain" -    refute retweeted_activity.local -    assert retweeted_object.data["announcement_count"] == 1 -    assert String.contains?(retweeted_object.data["content"], "mastodon") -    refute String.contains?(retweeted_object.data["content"], "Test account") -  end - -  test "handle incoming retweets - GS, subscription - local message" do -    incoming = File.read!("test/fixtures/share-gs-local.xml") -    note_activity = insert(:note_activity) -    object = Object.normalize(note_activity) -    user = User.get_cached_by_ap_id(note_activity.data["actor"]) - -    incoming = -      incoming -      |> String.replace("LOCAL_ID", object.data["id"]) -      |> String.replace("LOCAL_USER", user.ap_id) - -    {:ok, [[activity, retweeted_activity]]} = OStatus.handle_incoming(incoming) - -    assert activity.data["type"] == "Announce" -    assert activity.data["actor"] == "https://social.heldscal.la/user/23211" -    assert activity.data["object"] == object.data["id"] -    assert user.ap_id in activity.data["to"] -    refute activity.local - -    retweeted_activity = Activity.get_by_id(retweeted_activity.id) -    assert note_activity.id == retweeted_activity.id -    assert retweeted_activity.data["type"] == "Create" -    assert retweeted_activity.data["actor"] == user.ap_id -    assert retweeted_activity.local -    assert retweeted_activity.data["object"]["announcement_count"] == 1 -  end - -  test "handle incoming retweets - Mastodon, salmon" do -    incoming = File.read!("test/fixtures/share.xml") -    {:ok, [[activity, retweeted_activity]]} = OStatus.handle_incoming(incoming) -    retweeted_object = Object.normalize(retweeted_activity) - -    assert activity.data["type"] == "Announce" -    assert activity.data["actor"] == "https://mastodon.social/users/lambadalambda" -    assert activity.data["object"] == retweeted_activity.data["object"] - -    assert activity.data["id"] == -             "tag:mastodon.social,2017-05-03:objectId=4934452:objectType=Status" - -    refute activity.local -    assert retweeted_activity.data["type"] == "Create" -    assert retweeted_activity.data["actor"] == "https://pleroma.soykaf.com/users/lain" -    refute retweeted_activity.local -    refute String.contains?(retweeted_object.data["content"], "Test account") -  end - -  test "handle incoming favorites - GS, websub" do -    capture_log(fn -> -      incoming = File.read!("test/fixtures/favorite.xml") -      {:ok, [[activity, favorited_activity]]} = OStatus.handle_incoming(incoming) - -      assert activity.data["type"] == "Like" -      assert activity.data["actor"] == "https://social.heldscal.la/user/23211" -      assert activity.data["object"] == favorited_activity.data["object"] - -      assert activity.data["id"] == -               "tag:social.heldscal.la,2017-05-05:fave:23211:comment:2061643:2017-05-05T09:12:50+00:00" - -      refute activity.local -      assert favorited_activity.data["type"] == "Create" -      assert favorited_activity.data["actor"] == "https://shitposter.club/user/1" - -      assert favorited_activity.data["object"] == -               "tag:shitposter.club,2017-05-05:noticeId=2827873:objectType=comment" - -      refute favorited_activity.local -    end) -  end - -  test "handle conversation references" do -    incoming = File.read!("test/fixtures/mastodon_conversation.xml") -    {:ok, [activity]} = OStatus.handle_incoming(incoming) - -    assert activity.data["context"] == -             "tag:mastodon.social,2017-08-28:objectId=7876885:objectType=Conversation" -  end - -  test "handle incoming favorites with locally available object - GS, websub" do -    note_activity = insert(:note_activity) -    object = Object.normalize(note_activity) - -    incoming = -      File.read!("test/fixtures/favorite_with_local_note.xml") -      |> String.replace("localid", object.data["id"]) - -    {:ok, [[activity, favorited_activity]]} = OStatus.handle_incoming(incoming) - -    assert activity.data["type"] == "Like" -    assert activity.data["actor"] == "https://social.heldscal.la/user/23211" -    assert activity.data["object"] == object.data["id"] -    refute activity.local -    assert note_activity.id == favorited_activity.id -    assert favorited_activity.local -  end - -  test_with_mock "handle incoming replies, fetching replied-to activities if we don't have them", -                 OStatus, -                 [:passthrough], -                 [] do -    incoming = File.read!("test/fixtures/incoming_note_activity_answer.xml") -    {:ok, [activity]} = OStatus.handle_incoming(incoming) -    object = Object.normalize(activity, false) - -    assert activity.data["type"] == "Create" -    assert object.data["type"] == "Note" - -    assert object.data["inReplyTo"] == -             "http://pleroma.example.org:4000/objects/55bce8fc-b423-46b1-af71-3759ab4670bc" - -    assert "http://pleroma.example.org:4000/users/lain5" in activity.data["to"] - -    assert object.data["id"] == "tag:gs.example.org:4040,2017-04-25:noticeId=55:objectType=note" - -    assert "https://www.w3.org/ns/activitystreams#Public" in activity.data["to"] - -    assert called(OStatus.fetch_activity_from_url(object.data["inReplyTo"], :_)) -  end - -  test_with_mock "handle incoming replies, not fetching replied-to activities beyond max_replies_depth", -                 OStatus, -                 [:passthrough], -                 [] do -    incoming = File.read!("test/fixtures/incoming_note_activity_answer.xml") - -    with_mock Pleroma.Web.Federator, -      allowed_incoming_reply_depth?: fn _ -> false end do -      {:ok, [activity]} = OStatus.handle_incoming(incoming) -      object = Object.normalize(activity, false) - -      refute called(OStatus.fetch_activity_from_url(object.data["inReplyTo"], :_)) -    end -  end - -  test "handle incoming follows" do -    incoming = File.read!("test/fixtures/follow.xml") -    {:ok, [activity]} = OStatus.handle_incoming(incoming) -    assert activity.data["type"] == "Follow" - -    assert activity.data["id"] == -             "tag:social.heldscal.la,2017-05-07:subscription:23211:person:44803:2017-05-07T09:54:48+00:00" - -    assert activity.data["actor"] == "https://social.heldscal.la/user/23211" -    assert activity.data["object"] == "https://pawoo.net/users/pekorino" -    refute activity.local - -    follower = User.get_cached_by_ap_id(activity.data["actor"]) -    followed = User.get_cached_by_ap_id(activity.data["object"]) - -    assert User.following?(follower, followed) -  end - -  test "handle incoming unfollows with existing follow" do -    incoming_follow = File.read!("test/fixtures/follow.xml") -    {:ok, [_activity]} = OStatus.handle_incoming(incoming_follow) - -    incoming = File.read!("test/fixtures/unfollow.xml") -    {:ok, [activity]} = OStatus.handle_incoming(incoming) - -    assert activity.data["type"] == "Undo" - -    assert activity.data["id"] == -             "undo:tag:social.heldscal.la,2017-05-07:subscription:23211:person:44803:2017-05-07T09:54:48+00:00" - -    assert activity.data["actor"] == "https://social.heldscal.la/user/23211" -    embedded_object = activity.data["object"] -    assert is_map(embedded_object) -    assert embedded_object["type"] == "Follow" -    assert embedded_object["object"] == "https://pawoo.net/users/pekorino" -    refute activity.local - -    follower = User.get_cached_by_ap_id(activity.data["actor"]) -    followed = User.get_cached_by_ap_id(embedded_object["object"]) - -    refute User.following?(follower, followed) -  end - -  test "it clears `unreachable` federation status of the sender" do -    incoming_reaction_xml = File.read!("test/fixtures/share-gs.xml") -    doc = XML.parse_document(incoming_reaction_xml) -    actor_uri = XML.string_from_xpath("//author/uri[1]", doc) -    reacted_to_author_uri = XML.string_from_xpath("//author/uri[2]", doc) - -    Instances.set_consistently_unreachable(actor_uri) -    Instances.set_consistently_unreachable(reacted_to_author_uri) -    refute Instances.reachable?(actor_uri) -    refute Instances.reachable?(reacted_to_author_uri) - -    {:ok, _} = OStatus.handle_incoming(incoming_reaction_xml) -    assert Instances.reachable?(actor_uri) -    refute Instances.reachable?(reacted_to_author_uri) -  end - -  describe "new remote user creation" do -    test "returns local users" do -      local_user = insert(:user) -      {:ok, user} = OStatus.find_or_make_user(local_user.ap_id) - -      assert user == local_user -    end - -    test "tries to use the information in poco fields" do -      uri = "https://social.heldscal.la/user/23211" - -      {:ok, user} = OStatus.find_or_make_user(uri) - -      user = User.get_cached_by_id(user.id) -      assert user.name == "Constance Variable" -      assert user.nickname == "lambadalambda@social.heldscal.la" -      assert user.local == false -      assert user.info.uri == uri -      assert user.ap_id == uri -      assert user.bio == "Call me Deacon Blues." -      assert user.avatar["type"] == "Image" - -      {:ok, user_again} = OStatus.find_or_make_user(uri) - -      assert user == user_again -    end - -    test "find_or_make_user sets all the nessary input fields" do -      uri = "https://social.heldscal.la/user/23211" -      {:ok, user} = OStatus.find_or_make_user(uri) - -      assert user.info == -               %User.Info{ -                 id: user.info.id, -                 ap_enabled: false, -                 background: %{}, -                 banner: %{}, -                 blocks: [], -                 deactivated: false, -                 default_scope: "public", -                 domain_blocks: [], -                 follower_count: 0, -                 is_admin: false, -                 is_moderator: false, -                 keys: nil, -                 locked: false, -                 no_rich_text: false, -                 note_count: 0, -                 settings: nil, -                 source_data: %{}, -                 hub: "https://social.heldscal.la/main/push/hub", -                 magic_key: -                   "RSA.uzg6r1peZU0vXGADWxGJ0PE34WvmhjUmydbX5YYdOiXfODVLwCMi1umGoqUDm-mRu4vNEdFBVJU1CpFA7dKzWgIsqsa501i2XqElmEveXRLvNRWFB6nG03Q5OUY2as8eE54BJm0p20GkMfIJGwP6TSFb-ICp3QjzbatuSPJ6xCE=.AQAB", -                 salmon: "https://social.heldscal.la/main/salmon/user/23211", -                 topic: "https://social.heldscal.la/api/statuses/user_timeline/23211.atom", -                 uri: "https://social.heldscal.la/user/23211" -               } -    end - -    test "find_make_or_update_user takes an author element and returns an updated user" do -      uri = "https://social.heldscal.la/user/23211" - -      {:ok, user} = OStatus.find_or_make_user(uri) -      old_name = user.name -      old_bio = user.bio -      change = Ecto.Changeset.change(user, %{avatar: nil, bio: nil, name: nil}) - -      {:ok, user} = Repo.update(change) -      refute user.avatar - -      doc = XML.parse_document(File.read!("test/fixtures/23211.atom")) -      [author] = :xmerl_xpath.string('//author[1]', doc) -      {:ok, user} = OStatus.find_make_or_update_user(author) -      assert user.avatar["type"] == "Image" -      assert user.name == old_name -      assert user.bio == old_bio - -      {:ok, user_again} = OStatus.find_make_or_update_user(author) -      assert user_again == user -    end -  end - -  describe "gathering user info from a user id" do -    test "it returns user info in a hash" do -      user = "shp@social.heldscal.la" - -      # TODO: make test local -      {:ok, data} = OStatus.gather_user_info(user) - -      expected = %{ -        "hub" => "https://social.heldscal.la/main/push/hub", -        "magic_key" => -          "RSA.wQ3i9UA0qmAxZ0WTIp4a-waZn_17Ez1pEEmqmqoooRsG1_BvpmOvLN0G2tEcWWxl2KOtdQMCiPptmQObeZeuj48mdsDZ4ArQinexY2hCCTcbV8Xpswpkb8K05RcKipdg07pnI7tAgQ0VWSZDImncL6YUGlG5YN8b5TjGOwk2VG8=.AQAB", -        "name" => "shp", -        "nickname" => "shp", -        "salmon" => "https://social.heldscal.la/main/salmon/user/29191", -        "subject" => "acct:shp@social.heldscal.la", -        "topic" => "https://social.heldscal.la/api/statuses/user_timeline/29191.atom", -        "uri" => "https://social.heldscal.la/user/29191", -        "host" => "social.heldscal.la", -        "fqn" => user, -        "bio" => "cofe", -        "avatar" => %{ -          "type" => "Image", -          "url" => [ -            %{ -              "href" => "https://social.heldscal.la/avatar/29191-original-20170421154949.jpeg", -              "mediaType" => "image/jpeg", -              "type" => "Link" -            } -          ] -        }, -        "subscribe_address" => "https://social.heldscal.la/main/ostatussub?profile={uri}", -        "ap_id" => nil -      } - -      assert data == expected -    end - -    test "it works with the uri" do -      user = "https://social.heldscal.la/user/29191" - -      # TODO: make test local -      {:ok, data} = OStatus.gather_user_info(user) - -      expected = %{ -        "hub" => "https://social.heldscal.la/main/push/hub", -        "magic_key" => -          "RSA.wQ3i9UA0qmAxZ0WTIp4a-waZn_17Ez1pEEmqmqoooRsG1_BvpmOvLN0G2tEcWWxl2KOtdQMCiPptmQObeZeuj48mdsDZ4ArQinexY2hCCTcbV8Xpswpkb8K05RcKipdg07pnI7tAgQ0VWSZDImncL6YUGlG5YN8b5TjGOwk2VG8=.AQAB", -        "name" => "shp", -        "nickname" => "shp", -        "salmon" => "https://social.heldscal.la/main/salmon/user/29191", -        "subject" => "https://social.heldscal.la/user/29191", -        "topic" => "https://social.heldscal.la/api/statuses/user_timeline/29191.atom", -        "uri" => "https://social.heldscal.la/user/29191", -        "host" => "social.heldscal.la", -        "fqn" => user, -        "bio" => "cofe", -        "avatar" => %{ -          "type" => "Image", -          "url" => [ -            %{ -              "href" => "https://social.heldscal.la/avatar/29191-original-20170421154949.jpeg", -              "mediaType" => "image/jpeg", -              "type" => "Link" -            } -          ] -        }, -        "subscribe_address" => "https://social.heldscal.la/main/ostatussub?profile={uri}", -        "ap_id" => nil -      } - -      assert data == expected -    end -  end - -  describe "fetching a status by it's HTML url" do -    test "it builds a missing status from an html url" do -      capture_log(fn -> -        url = "https://shitposter.club/notice/2827873" -        {:ok, [activity]} = OStatus.fetch_activity_from_url(url) - -        assert activity.data["actor"] == "https://shitposter.club/user/1" - -        assert activity.data["object"] == -                 "tag:shitposter.club,2017-05-05:noticeId=2827873:objectType=comment" -      end) -    end - -    test "it works for atom notes, too" do -      url = "https://social.sakamoto.gq/objects/0ccc1a2c-66b0-4305-b23a-7f7f2b040056" -      {:ok, [activity]} = OStatus.fetch_activity_from_url(url) -      assert activity.data["actor"] == "https://social.sakamoto.gq/users/eal" -      assert activity.data["object"] == url -    end -  end - -  test "it doesn't add nil in the to field" do -    incoming = File.read!("test/fixtures/nil_mention_entry.xml") -    {:ok, [activity]} = OStatus.handle_incoming(incoming) - -    assert activity.data["to"] == [ -             "http://localhost:4001/users/atarifrosch@social.stopwatchingus-heidelberg.de/followers", -             "https://www.w3.org/ns/activitystreams#Public" -           ] -  end - -  describe "is_representable?" do -    test "Note objects are representable" do -      note_activity = insert(:note_activity) - -      assert OStatus.is_representable?(note_activity) -    end - -    test "Article objects are not representable" do -      note_activity = insert(:note_activity) -      note_object = Object.normalize(note_activity) - -      note_data = -        note_object.data -        |> Map.put("type", "Article") - -      Cachex.clear(:object_cache) - -      cs = Object.change(note_object, %{data: note_data}) -      {:ok, _article_object} = Repo.update(cs) - -      # the underlying object is now an Article instead of a note, so this should fail -      refute OStatus.is_representable?(note_activity) -    end -  end -end diff --git a/test/web/ostatus/user_representer_test.exs b/test/web/ostatus/user_representer_test.exs deleted file mode 100644 index e3863d2e9..000000000 --- a/test/web/ostatus/user_representer_test.exs +++ /dev/null @@ -1,38 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/> -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Web.OStatus.UserRepresenterTest do -  use Pleroma.DataCase -  alias Pleroma.Web.OStatus.UserRepresenter - -  import Pleroma.Factory -  alias Pleroma.User - -  test "returns a user with id, uri, name and link" do -    user = insert(:user, %{nickname: "レイン"}) -    tuple = UserRepresenter.to_simple_form(user) - -    res = :xmerl.export_simple_content(tuple, :xmerl_xml) |> to_string - -    expected = """ -    <id>#{user.ap_id}</id> -    <activity:object>http://activitystrea.ms/schema/1.0/person</activity:object> -    <uri>#{user.ap_id}</uri> -    <poco:preferredUsername>#{user.nickname}</poco:preferredUsername> -    <poco:displayName>#{user.name}</poco:displayName> -    <poco:note>#{user.bio}</poco:note> -    <summary>#{user.bio}</summary> -    <name>#{user.nickname}</name> -    <link rel="avatar" href="#{User.avatar_url(user)}" /> -    <link rel="header" href="#{User.banner_url(user)}" /> -    <ap_enabled>true</ap_enabled> -    """ - -    assert clean(res) == clean(expected) -  end - -  defp clean(string) do -    String.replace(string, ~r/\s/, "") -  end -end diff --git a/test/web/pleroma_api/controllers/account_controller_test.exs b/test/web/pleroma_api/controllers/account_controller_test.exs new file mode 100644 index 000000000..d17026a6b --- /dev/null +++ b/test/web/pleroma_api/controllers/account_controller_test.exs @@ -0,0 +1,338 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.PleromaAPI.AccountControllerTest do +  use Pleroma.Web.ConnCase + +  alias Pleroma.Config +  alias Pleroma.Tests.ObanHelpers +  alias Pleroma.User +  alias Pleroma.Web.CommonAPI + +  import Pleroma.Factory +  import Swoosh.TestAssertions + +  @image "data:image/gif;base64,R0lGODlhEAAQAMQAAORHHOVSKudfOulrSOp3WOyDZu6QdvCchPGolfO0o/XBs/fNwfjZ0frl3/zy7////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAkAABAALAAAAAAQABAAAAVVICSOZGlCQAosJ6mu7fiyZeKqNKToQGDsM8hBADgUXoGAiqhSvp5QAnQKGIgUhwFUYLCVDFCrKUE1lBavAViFIDlTImbKC5Gm2hB0SlBCBMQiB0UjIQA7" + +  describe "POST /api/v1/pleroma/accounts/confirmation_resend" do +    setup do +      {:ok, user} = +        insert(:user) +        |> User.confirmation_changeset(need_confirmation: true) +        |> User.update_and_set_cache() + +      assert user.confirmation_pending + +      [user: user] +    end + +    clear_config([:instance, :account_activation_required]) do +      Config.put([:instance, :account_activation_required], true) +    end + +    test "resend account confirmation email", %{conn: conn, user: user} do +      conn +      |> post("/api/v1/pleroma/accounts/confirmation_resend?email=#{user.email}") +      |> json_response(:no_content) + +      ObanHelpers.perform_all() + +      email = Pleroma.Emails.UserEmail.account_confirmation_email(user) +      notify_email = Config.get([:instance, :notify_email]) +      instance_name = Config.get([:instance, :name]) + +      assert_email_sent( +        from: {instance_name, notify_email}, +        to: {user.name, user.email}, +        html_body: email.html_body +      ) +    end +  end + +  describe "PATCH /api/v1/pleroma/accounts/update_avatar" do +    setup do: oauth_access(["write:accounts"]) + +    test "user avatar can be set", %{user: user, conn: conn} do +      avatar_image = File.read!("test/fixtures/avatar_data_uri") + +      conn = patch(conn, "/api/v1/pleroma/accounts/update_avatar", %{img: avatar_image}) + +      user = refresh_record(user) + +      assert %{ +               "name" => _, +               "type" => _, +               "url" => [ +                 %{ +                   "href" => _, +                   "mediaType" => _, +                   "type" => _ +                 } +               ] +             } = user.avatar + +      assert %{"url" => _} = json_response(conn, 200) +    end + +    test "user avatar can be reset", %{user: user, conn: conn} do +      conn = patch(conn, "/api/v1/pleroma/accounts/update_avatar", %{img: ""}) + +      user = User.get_cached_by_id(user.id) + +      assert user.avatar == nil + +      assert %{"url" => nil} = json_response(conn, 200) +    end +  end + +  describe "PATCH /api/v1/pleroma/accounts/update_banner" do +    setup do: oauth_access(["write:accounts"]) + +    test "can set profile banner", %{user: user, conn: conn} do +      conn = patch(conn, "/api/v1/pleroma/accounts/update_banner", %{"banner" => @image}) + +      user = refresh_record(user) +      assert user.banner["type"] == "Image" + +      assert %{"url" => _} = json_response(conn, 200) +    end + +    test "can reset profile banner", %{user: user, conn: conn} do +      conn = patch(conn, "/api/v1/pleroma/accounts/update_banner", %{"banner" => ""}) + +      user = refresh_record(user) +      assert user.banner == %{} + +      assert %{"url" => nil} = json_response(conn, 200) +    end +  end + +  describe "PATCH /api/v1/pleroma/accounts/update_background" do +    setup do: oauth_access(["write:accounts"]) + +    test "background image can be set", %{user: user, conn: conn} do +      conn = patch(conn, "/api/v1/pleroma/accounts/update_background", %{"img" => @image}) + +      user = refresh_record(user) +      assert user.background["type"] == "Image" +      assert %{"url" => _} = json_response(conn, 200) +    end + +    test "background image can be reset", %{user: user, conn: conn} do +      conn = patch(conn, "/api/v1/pleroma/accounts/update_background", %{"img" => ""}) + +      user = refresh_record(user) +      assert user.background == %{} +      assert %{"url" => nil} = json_response(conn, 200) +    end +  end + +  describe "getting favorites timeline of specified user" do +    setup do +      [current_user, user] = insert_pair(:user, hide_favorites: false) +      %{user: current_user, conn: conn} = oauth_access(["read:favourites"], user: current_user) +      [current_user: current_user, user: user, conn: conn] +    end + +    test "returns list of statuses favorited by specified user", %{ +      conn: conn, +      user: user +    } do +      [activity | _] = insert_pair(:note_activity) +      CommonAPI.favorite(activity.id, user) + +      response = +        conn +        |> get("/api/v1/pleroma/accounts/#{user.id}/favourites") +        |> json_response(:ok) + +      [like] = response + +      assert length(response) == 1 +      assert like["id"] == activity.id +    end + +    test "does not return favorites for specified user_id when user is not logged in", %{ +      user: user +    } do +      activity = insert(:note_activity) +      CommonAPI.favorite(activity.id, user) + +      build_conn() +      |> get("/api/v1/pleroma/accounts/#{user.id}/favourites") +      |> json_response(403) +    end + +    test "returns favorited DM only when user is logged in and he is one of recipients", %{ +      current_user: current_user, +      user: user +    } do +      {:ok, direct} = +        CommonAPI.post(current_user, %{ +          "status" => "Hi @#{user.nickname}!", +          "visibility" => "direct" +        }) + +      CommonAPI.favorite(direct.id, user) + +      for u <- [user, current_user] do +        response = +          build_conn() +          |> assign(:user, u) +          |> assign(:token, insert(:oauth_token, user: u, scopes: ["read:favourites"])) +          |> get("/api/v1/pleroma/accounts/#{user.id}/favourites") +          |> json_response(:ok) + +        assert length(response) == 1 +      end + +      build_conn() +      |> get("/api/v1/pleroma/accounts/#{user.id}/favourites") +      |> json_response(403) +    end + +    test "does not return others' favorited DM when user is not one of recipients", %{ +      conn: conn, +      user: user +    } do +      user_two = insert(:user) + +      {:ok, direct} = +        CommonAPI.post(user_two, %{ +          "status" => "Hi @#{user.nickname}!", +          "visibility" => "direct" +        }) + +      CommonAPI.favorite(direct.id, user) + +      response = +        conn +        |> get("/api/v1/pleroma/accounts/#{user.id}/favourites") +        |> json_response(:ok) + +      assert Enum.empty?(response) +    end + +    test "paginates favorites using since_id and max_id", %{ +      conn: conn, +      user: user +    } do +      activities = insert_list(10, :note_activity) + +      Enum.each(activities, fn activity -> +        CommonAPI.favorite(activity.id, user) +      end) + +      third_activity = Enum.at(activities, 2) +      seventh_activity = Enum.at(activities, 6) + +      response = +        conn +        |> get("/api/v1/pleroma/accounts/#{user.id}/favourites", %{ +          since_id: third_activity.id, +          max_id: seventh_activity.id +        }) +        |> json_response(:ok) + +      assert length(response) == 3 +      refute third_activity in response +      refute seventh_activity in response +    end + +    test "limits favorites using limit parameter", %{ +      conn: conn, +      user: user +    } do +      7 +      |> insert_list(:note_activity) +      |> Enum.each(fn activity -> +        CommonAPI.favorite(activity.id, user) +      end) + +      response = +        conn +        |> get("/api/v1/pleroma/accounts/#{user.id}/favourites", %{limit: "3"}) +        |> json_response(:ok) + +      assert length(response) == 3 +    end + +    test "returns empty response when user does not have any favorited statuses", %{ +      conn: conn, +      user: user +    } do +      response = +        conn +        |> get("/api/v1/pleroma/accounts/#{user.id}/favourites") +        |> json_response(:ok) + +      assert Enum.empty?(response) +    end + +    test "returns 404 error when specified user is not exist", %{conn: conn} do +      conn = get(conn, "/api/v1/pleroma/accounts/test/favourites") + +      assert json_response(conn, 404) == %{"error" => "Record not found"} +    end + +    test "returns 403 error when user has hidden own favorites", %{conn: conn} do +      user = insert(:user, hide_favorites: true) +      activity = insert(:note_activity) +      CommonAPI.favorite(activity.id, user) + +      conn = get(conn, "/api/v1/pleroma/accounts/#{user.id}/favourites") + +      assert json_response(conn, 403) == %{"error" => "Can't get favorites"} +    end + +    test "hides favorites for new users by default", %{conn: conn} do +      user = insert(:user) +      activity = insert(:note_activity) +      CommonAPI.favorite(activity.id, user) + +      assert user.hide_favorites +      conn = get(conn, "/api/v1/pleroma/accounts/#{user.id}/favourites") + +      assert json_response(conn, 403) == %{"error" => "Can't get favorites"} +    end +  end + +  describe "subscribing / unsubscribing" do +    test "subscribing / unsubscribing to a user" do +      %{user: user, conn: conn} = oauth_access(["follow"]) +      subscription_target = insert(:user) + +      ret_conn = +        conn +        |> assign(:user, user) +        |> post("/api/v1/pleroma/accounts/#{subscription_target.id}/subscribe") + +      assert %{"id" => _id, "subscribing" => true} = json_response(ret_conn, 200) + +      conn = post(conn, "/api/v1/pleroma/accounts/#{subscription_target.id}/unsubscribe") + +      assert %{"id" => _id, "subscribing" => false} = json_response(conn, 200) +    end +  end + +  describe "subscribing" do +    test "returns 404 when subscription_target not found" do +      %{conn: conn} = oauth_access(["write:follows"]) + +      conn = post(conn, "/api/v1/pleroma/accounts/target_id/subscribe") + +      assert %{"error" => "Record not found"} = json_response(conn, 404) +    end +  end + +  describe "unsubscribing" do +    test "returns 404 when subscription_target not found" do +      %{conn: conn} = oauth_access(["follow"]) + +      conn = post(conn, "/api/v1/pleroma/accounts/target_id/unsubscribe") + +      assert %{"error" => "Record not found"} = json_response(conn, 404) +    end +  end +end diff --git a/test/web/pleroma_api/controllers/emoji_api_controller_test.exs b/test/web/pleroma_api/controllers/emoji_api_controller_test.exs new file mode 100644 index 000000000..8e76f2f3d --- /dev/null +++ b/test/web/pleroma_api/controllers/emoji_api_controller_test.exs @@ -0,0 +1,467 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.PleromaAPI.EmojiAPIControllerTest do +  use Pleroma.Web.ConnCase + +  import Tesla.Mock + +  import Pleroma.Factory + +  @emoji_dir_path Path.join( +                    Pleroma.Config.get!([:instance, :static_dir]), +                    "emoji" +                  ) + +  clear_config([:auth, :enforce_oauth_admin_scope_usage]) do +    Pleroma.Config.put([:auth, :enforce_oauth_admin_scope_usage], false) +  end + +  test "shared & non-shared pack information in list_packs is ok" do +    conn = build_conn() +    resp = conn |> get(emoji_api_path(conn, :list_packs)) |> json_response(200) + +    assert Map.has_key?(resp, "test_pack") + +    pack = resp["test_pack"] + +    assert Map.has_key?(pack["pack"], "download-sha256") +    assert pack["pack"]["can-download"] + +    assert pack["files"] == %{"blank" => "blank.png"} + +    # Non-shared pack + +    assert Map.has_key?(resp, "test_pack_nonshared") + +    pack = resp["test_pack_nonshared"] + +    refute pack["pack"]["shared"] +    refute pack["pack"]["can-download"] +  end + +  test "listing remote packs" do +    admin = insert(:user, is_admin: true) +    %{conn: conn} = oauth_access(["admin:write"], user: admin) + +    resp = +      build_conn() +      |> get(emoji_api_path(conn, :list_packs)) +      |> json_response(200) + +    mock(fn +      %{method: :get, url: "https://example.com/.well-known/nodeinfo"} -> +        json(%{links: [%{href: "https://example.com/nodeinfo/2.1.json"}]}) + +      %{method: :get, url: "https://example.com/nodeinfo/2.1.json"} -> +        json(%{metadata: %{features: ["shareable_emoji_packs"]}}) + +      %{method: :get, url: "https://example.com/api/pleroma/emoji/packs"} -> +        json(resp) +    end) + +    assert conn +           |> post(emoji_api_path(conn, :list_from), %{instance_address: "https://example.com"}) +           |> json_response(200) == resp +  end + +  test "downloading a shared pack from download_shared" do +    conn = build_conn() + +    resp = +      conn +      |> get(emoji_api_path(conn, :download_shared, "test_pack")) +      |> response(200) + +    {:ok, arch} = :zip.unzip(resp, [:memory]) + +    assert Enum.find(arch, fn {n, _} -> n == 'pack.json' end) +    assert Enum.find(arch, fn {n, _} -> n == 'blank.png' end) +  end + +  test "downloading shared & unshared packs from another instance via download_from, deleting them" do +    on_exit(fn -> +      File.rm_rf!("#{@emoji_dir_path}/test_pack2") +      File.rm_rf!("#{@emoji_dir_path}/test_pack_nonshared2") +    end) + +    mock(fn +      %{method: :get, url: "https://old-instance/.well-known/nodeinfo"} -> +        json(%{links: [%{href: "https://old-instance/nodeinfo/2.1.json"}]}) + +      %{method: :get, url: "https://old-instance/nodeinfo/2.1.json"} -> +        json(%{metadata: %{features: []}}) + +      %{method: :get, url: "https://example.com/.well-known/nodeinfo"} -> +        json(%{links: [%{href: "https://example.com/nodeinfo/2.1.json"}]}) + +      %{method: :get, url: "https://example.com/nodeinfo/2.1.json"} -> +        json(%{metadata: %{features: ["shareable_emoji_packs"]}}) + +      %{ +        method: :get, +        url: "https://example.com/api/pleroma/emoji/packs/list" +      } -> +        conn = build_conn() + +        conn +        |> get(emoji_api_path(conn, :list_packs)) +        |> json_response(200) +        |> json() + +      %{ +        method: :get, +        url: "https://example.com/api/pleroma/emoji/packs/download_shared/test_pack" +      } -> +        conn = build_conn() + +        conn +        |> get(emoji_api_path(conn, :download_shared, "test_pack")) +        |> response(200) +        |> text() + +      %{ +        method: :get, +        url: "https://nonshared-pack" +      } -> +        text(File.read!("#{@emoji_dir_path}/test_pack_nonshared/nonshared.zip")) +    end) + +    admin = insert(:user, is_admin: true) + +    conn = +      build_conn() +      |> assign(:user, admin) +      |> assign(:token, insert(:oauth_admin_token, user: admin, scopes: ["admin:write"])) + +    assert (conn +            |> put_req_header("content-type", "application/json") +            |> post( +              emoji_api_path( +                conn, +                :download_from +              ), +              %{ +                instance_address: "https://old-instance", +                pack_name: "test_pack", +                as: "test_pack2" +              } +              |> Jason.encode!() +            ) +            |> json_response(500))["error"] =~ "does not support" + +    assert conn +           |> put_req_header("content-type", "application/json") +           |> post( +             emoji_api_path( +               conn, +               :download_from +             ), +             %{ +               instance_address: "https://example.com", +               pack_name: "test_pack", +               as: "test_pack2" +             } +             |> Jason.encode!() +           ) +           |> json_response(200) == "ok" + +    assert File.exists?("#{@emoji_dir_path}/test_pack2/pack.json") +    assert File.exists?("#{@emoji_dir_path}/test_pack2/blank.png") + +    assert conn +           |> delete(emoji_api_path(conn, :delete, "test_pack2")) +           |> json_response(200) == "ok" + +    refute File.exists?("#{@emoji_dir_path}/test_pack2") + +    # non-shared, downloaded from the fallback URL + +    assert conn +           |> put_req_header("content-type", "application/json") +           |> post( +             emoji_api_path( +               conn, +               :download_from +             ), +             %{ +               instance_address: "https://example.com", +               pack_name: "test_pack_nonshared", +               as: "test_pack_nonshared2" +             } +             |> Jason.encode!() +           ) +           |> json_response(200) == "ok" + +    assert File.exists?("#{@emoji_dir_path}/test_pack_nonshared2/pack.json") +    assert File.exists?("#{@emoji_dir_path}/test_pack_nonshared2/blank.png") + +    assert conn +           |> delete(emoji_api_path(conn, :delete, "test_pack_nonshared2")) +           |> json_response(200) == "ok" + +    refute File.exists?("#{@emoji_dir_path}/test_pack_nonshared2") +  end + +  describe "updating pack metadata" do +    setup do +      pack_file = "#{@emoji_dir_path}/test_pack/pack.json" +      original_content = File.read!(pack_file) + +      on_exit(fn -> +        File.write!(pack_file, original_content) +      end) + +      admin = insert(:user, is_admin: true) +      %{conn: conn} = oauth_access(["admin:write"], user: admin) + +      {:ok, +       admin: admin, +       conn: conn, +       pack_file: pack_file, +       new_data: %{ +         "license" => "Test license changed", +         "homepage" => "https://pleroma.social", +         "description" => "Test description", +         "share-files" => false +       }} +    end + +    test "for a pack without a fallback source", ctx do +      conn = ctx[:conn] + +      assert conn +             |> post( +               emoji_api_path(conn, :update_metadata, "test_pack"), +               %{ +                 "new_data" => ctx[:new_data] +               } +             ) +             |> json_response(200) == ctx[:new_data] + +      assert Jason.decode!(File.read!(ctx[:pack_file]))["pack"] == ctx[:new_data] +    end + +    test "for a pack with a fallback source", ctx do +      mock(fn +        %{ +          method: :get, +          url: "https://nonshared-pack" +        } -> +          text(File.read!("#{@emoji_dir_path}/test_pack_nonshared/nonshared.zip")) +      end) + +      new_data = Map.put(ctx[:new_data], "fallback-src", "https://nonshared-pack") + +      new_data_with_sha = +        Map.put( +          new_data, +          "fallback-src-sha256", +          "74409E2674DAA06C072729C6C8426C4CB3B7E0B85ED77792DB7A436E11D76DAF" +        ) + +      conn = ctx[:conn] + +      assert conn +             |> post( +               emoji_api_path(conn, :update_metadata, "test_pack"), +               %{ +                 "new_data" => new_data +               } +             ) +             |> json_response(200) == new_data_with_sha + +      assert Jason.decode!(File.read!(ctx[:pack_file]))["pack"] == new_data_with_sha +    end + +    test "when the fallback source doesn't have all the files", ctx do +      mock(fn +        %{ +          method: :get, +          url: "https://nonshared-pack" +        } -> +          {:ok, {'empty.zip', empty_arch}} = :zip.zip('empty.zip', [], [:memory]) +          text(empty_arch) +      end) + +      new_data = Map.put(ctx[:new_data], "fallback-src", "https://nonshared-pack") + +      conn = ctx[:conn] + +      assert (conn +              |> post( +                emoji_api_path(conn, :update_metadata, "test_pack"), +                %{ +                  "new_data" => new_data +                } +              ) +              |> json_response(:bad_request))["error"] =~ "does not have all" +    end +  end + +  test "updating pack files" do +    pack_file = "#{@emoji_dir_path}/test_pack/pack.json" +    original_content = File.read!(pack_file) + +    on_exit(fn -> +      File.write!(pack_file, original_content) + +      File.rm_rf!("#{@emoji_dir_path}/test_pack/blank_url.png") +      File.rm_rf!("#{@emoji_dir_path}/test_pack/dir") +      File.rm_rf!("#{@emoji_dir_path}/test_pack/dir_2") +    end) + +    admin = insert(:user, is_admin: true) +    %{conn: conn} = oauth_access(["admin:write"], user: admin) + +    same_name = %{ +      "action" => "add", +      "shortcode" => "blank", +      "filename" => "dir/blank.png", +      "file" => %Plug.Upload{ +        filename: "blank.png", +        path: "#{@emoji_dir_path}/test_pack/blank.png" +      } +    } + +    different_name = %{same_name | "shortcode" => "blank_2"} + +    assert (conn +            |> post(emoji_api_path(conn, :update_file, "test_pack"), same_name) +            |> json_response(:conflict))["error"] =~ "already exists" + +    assert conn +           |> post(emoji_api_path(conn, :update_file, "test_pack"), different_name) +           |> json_response(200) == %{"blank" => "blank.png", "blank_2" => "dir/blank.png"} + +    assert File.exists?("#{@emoji_dir_path}/test_pack/dir/blank.png") + +    assert conn +           |> post(emoji_api_path(conn, :update_file, "test_pack"), %{ +             "action" => "update", +             "shortcode" => "blank_2", +             "new_shortcode" => "blank_3", +             "new_filename" => "dir_2/blank_3.png" +           }) +           |> json_response(200) == %{"blank" => "blank.png", "blank_3" => "dir_2/blank_3.png"} + +    refute File.exists?("#{@emoji_dir_path}/test_pack/dir/") +    assert File.exists?("#{@emoji_dir_path}/test_pack/dir_2/blank_3.png") + +    assert conn +           |> post(emoji_api_path(conn, :update_file, "test_pack"), %{ +             "action" => "remove", +             "shortcode" => "blank_3" +           }) +           |> json_response(200) == %{"blank" => "blank.png"} + +    refute File.exists?("#{@emoji_dir_path}/test_pack/dir_2/") + +    mock(fn +      %{ +        method: :get, +        url: "https://test-blank/blank_url.png" +      } -> +        text(File.read!("#{@emoji_dir_path}/test_pack/blank.png")) +    end) + +    # The name should be inferred from the URL ending +    from_url = %{ +      "action" => "add", +      "shortcode" => "blank_url", +      "file" => "https://test-blank/blank_url.png" +    } + +    assert conn +           |> post(emoji_api_path(conn, :update_file, "test_pack"), from_url) +           |> json_response(200) == %{ +             "blank" => "blank.png", +             "blank_url" => "blank_url.png" +           } + +    assert File.exists?("#{@emoji_dir_path}/test_pack/blank_url.png") + +    assert conn +           |> post(emoji_api_path(conn, :update_file, "test_pack"), %{ +             "action" => "remove", +             "shortcode" => "blank_url" +           }) +           |> json_response(200) == %{"blank" => "blank.png"} + +    refute File.exists?("#{@emoji_dir_path}/test_pack/blank_url.png") +  end + +  test "creating and deleting a pack" do +    on_exit(fn -> +      File.rm_rf!("#{@emoji_dir_path}/test_created") +    end) + +    admin = insert(:user, is_admin: true) +    %{conn: conn} = oauth_access(["admin:write"], user: admin) + +    assert conn +           |> put_req_header("content-type", "application/json") +           |> put( +             emoji_api_path( +               conn, +               :create, +               "test_created" +             ) +           ) +           |> json_response(200) == "ok" + +    assert File.exists?("#{@emoji_dir_path}/test_created/pack.json") + +    assert Jason.decode!(File.read!("#{@emoji_dir_path}/test_created/pack.json")) == %{ +             "pack" => %{}, +             "files" => %{} +           } + +    assert conn +           |> delete(emoji_api_path(conn, :delete, "test_created")) +           |> json_response(200) == "ok" + +    refute File.exists?("#{@emoji_dir_path}/test_created/pack.json") +  end + +  test "filesystem import" do +    on_exit(fn -> +      File.rm!("#{@emoji_dir_path}/test_pack_for_import/emoji.txt") +      File.rm!("#{@emoji_dir_path}/test_pack_for_import/pack.json") +    end) + +    conn = build_conn() +    resp = conn |> get(emoji_api_path(conn, :list_packs)) |> json_response(200) + +    refute Map.has_key?(resp, "test_pack_for_import") + +    admin = insert(:user, is_admin: true) +    %{conn: conn} = oauth_access(["admin:write"], user: admin) + +    assert conn +           |> post(emoji_api_path(conn, :import_from_fs)) +           |> json_response(200) == ["test_pack_for_import"] + +    resp = conn |> get(emoji_api_path(conn, :list_packs)) |> json_response(200) +    assert resp["test_pack_for_import"]["files"] == %{"blank" => "blank.png"} + +    File.rm!("#{@emoji_dir_path}/test_pack_for_import/pack.json") +    refute File.exists?("#{@emoji_dir_path}/test_pack_for_import/pack.json") + +    emoji_txt_content = "blank, blank.png, Fun\n\nblank2, blank.png" + +    File.write!("#{@emoji_dir_path}/test_pack_for_import/emoji.txt", emoji_txt_content) + +    assert conn +           |> post(emoji_api_path(conn, :import_from_fs)) +           |> json_response(200) == ["test_pack_for_import"] + +    resp = build_conn() |> get(emoji_api_path(conn, :list_packs)) |> json_response(200) + +    assert resp["test_pack_for_import"]["files"] == %{ +             "blank" => "blank.png", +             "blank2" => "blank.png" +           } +  end +end diff --git a/test/web/pleroma_api/controllers/mascot_controller_test.exs b/test/web/pleroma_api/controllers/mascot_controller_test.exs new file mode 100644 index 000000000..40c33e609 --- /dev/null +++ b/test/web/pleroma_api/controllers/mascot_controller_test.exs @@ -0,0 +1,64 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.PleromaAPI.MascotControllerTest do +  use Pleroma.Web.ConnCase + +  alias Pleroma.User + +  test "mascot upload" do +    %{conn: conn} = oauth_access(["write:accounts"]) + +    non_image_file = %Plug.Upload{ +      content_type: "audio/mpeg", +      path: Path.absname("test/fixtures/sound.mp3"), +      filename: "sound.mp3" +    } + +    ret_conn = put(conn, "/api/v1/pleroma/mascot", %{"file" => non_image_file}) + +    assert json_response(ret_conn, 415) + +    file = %Plug.Upload{ +      content_type: "image/jpg", +      path: Path.absname("test/fixtures/image.jpg"), +      filename: "an_image.jpg" +    } + +    conn = put(conn, "/api/v1/pleroma/mascot", %{"file" => file}) + +    assert %{"id" => _, "type" => image} = json_response(conn, 200) +  end + +  test "mascot retrieving" do +    %{user: user, conn: conn} = oauth_access(["read:accounts", "write:accounts"]) + +    # When user hasn't set a mascot, we should just get pleroma tan back +    ret_conn = get(conn, "/api/v1/pleroma/mascot") + +    assert %{"url" => url} = json_response(ret_conn, 200) +    assert url =~ "pleroma-fox-tan-smol" + +    # When a user sets their mascot, we should get that back +    file = %Plug.Upload{ +      content_type: "image/jpg", +      path: Path.absname("test/fixtures/image.jpg"), +      filename: "an_image.jpg" +    } + +    ret_conn = put(conn, "/api/v1/pleroma/mascot", %{"file" => file}) + +    assert json_response(ret_conn, 200) + +    user = User.get_cached_by_id(user.id) + +    conn = +      conn +      |> assign(:user, user) +      |> get("/api/v1/pleroma/mascot") + +    assert %{"url" => url, "type" => "image"} = json_response(conn, 200) +    assert url =~ "an_image" +  end +end diff --git a/test/web/pleroma_api/controllers/pleroma_api_controller_test.exs b/test/web/pleroma_api/controllers/pleroma_api_controller_test.exs new file mode 100644 index 000000000..3978c2ec5 --- /dev/null +++ b/test/web/pleroma_api/controllers/pleroma_api_controller_test.exs @@ -0,0 +1,232 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.PleromaAPI.PleromaAPIControllerTest do +  use Pleroma.Web.ConnCase + +  alias Pleroma.Conversation.Participation +  alias Pleroma.Notification +  alias Pleroma.Object +  alias Pleroma.Repo +  alias Pleroma.User +  alias Pleroma.Web.CommonAPI + +  import Pleroma.Factory + +  test "POST /api/v1/pleroma/statuses/:id/react_with_emoji", %{conn: conn} do +    user = insert(:user) +    other_user = insert(:user) + +    {:ok, activity} = CommonAPI.post(user, %{"status" => "#cofe"}) + +    result = +      conn +      |> assign(:user, other_user) +      |> assign(:token, insert(:oauth_token, user: other_user, scopes: ["write:statuses"])) +      |> post("/api/v1/pleroma/statuses/#{activity.id}/react_with_emoji", %{"emoji" => "☕"}) + +    assert %{"id" => id} = json_response(result, 200) +    assert to_string(activity.id) == id +  end + +  test "POST /api/v1/pleroma/statuses/:id/unreact_with_emoji", %{conn: conn} do +    user = insert(:user) +    other_user = insert(:user) + +    {:ok, activity} = CommonAPI.post(user, %{"status" => "#cofe"}) +    {:ok, activity, _object} = CommonAPI.react_with_emoji(activity.id, other_user, "☕") + +    result = +      conn +      |> assign(:user, other_user) +      |> assign(:token, insert(:oauth_token, user: other_user, scopes: ["write:statuses"])) +      |> post("/api/v1/pleroma/statuses/#{activity.id}/unreact_with_emoji", %{"emoji" => "☕"}) + +    assert %{"id" => id} = json_response(result, 200) +    assert to_string(activity.id) == id + +    object = Object.normalize(activity) + +    assert object.data["reaction_count"] == 0 +  end + +  test "GET /api/v1/pleroma/statuses/:id/emoji_reactions_by", %{conn: conn} do +    user = insert(:user) +    other_user = insert(:user) + +    {:ok, activity} = CommonAPI.post(user, %{"status" => "#cofe"}) + +    result = +      conn +      |> get("/api/v1/pleroma/statuses/#{activity.id}/emoji_reactions_by") +      |> json_response(200) + +    assert result == [] + +    {:ok, _, _} = CommonAPI.react_with_emoji(activity.id, other_user, "🎅") + +    result = +      conn +      |> get("/api/v1/pleroma/statuses/#{activity.id}/emoji_reactions_by") +      |> json_response(200) + +    [%{"emoji" => "🎅", "count" => 1, "accounts" => [represented_user]}] = result +    assert represented_user["id"] == other_user.id +  end + +  test "/api/v1/pleroma/conversations/:id" do +    user = insert(:user) +    %{user: other_user, conn: conn} = oauth_access(["read:statuses"]) + +    {:ok, _activity} = +      CommonAPI.post(user, %{"status" => "Hi @#{other_user.nickname}!", "visibility" => "direct"}) + +    [participation] = Participation.for_user(other_user) + +    result = +      conn +      |> get("/api/v1/pleroma/conversations/#{participation.id}") +      |> json_response(200) + +    assert result["id"] == participation.id |> to_string() +  end + +  test "/api/v1/pleroma/conversations/:id/statuses" do +    user = insert(:user) +    %{user: other_user, conn: conn} = oauth_access(["read:statuses"]) +    third_user = insert(:user) + +    {:ok, _activity} = +      CommonAPI.post(user, %{"status" => "Hi @#{third_user.nickname}!", "visibility" => "direct"}) + +    {:ok, activity} = +      CommonAPI.post(user, %{"status" => "Hi @#{other_user.nickname}!", "visibility" => "direct"}) + +    [participation] = Participation.for_user(other_user) + +    {:ok, activity_two} = +      CommonAPI.post(other_user, %{ +        "status" => "Hi!", +        "in_reply_to_status_id" => activity.id, +        "in_reply_to_conversation_id" => participation.id +      }) + +    result = +      conn +      |> get("/api/v1/pleroma/conversations/#{participation.id}/statuses") +      |> json_response(200) + +    assert length(result) == 2 + +    id_one = activity.id +    id_two = activity_two.id +    assert [%{"id" => ^id_one}, %{"id" => ^id_two}] = result +  end + +  test "PATCH /api/v1/pleroma/conversations/:id" do +    %{user: user, conn: conn} = oauth_access(["write:conversations"]) +    other_user = insert(:user) + +    {:ok, _activity} = CommonAPI.post(user, %{"status" => "Hi", "visibility" => "direct"}) + +    [participation] = Participation.for_user(user) + +    participation = Repo.preload(participation, :recipients) + +    user = User.get_cached_by_id(user.id) +    assert [user] == participation.recipients +    assert other_user not in participation.recipients + +    result = +      conn +      |> patch("/api/v1/pleroma/conversations/#{participation.id}", %{ +        "recipients" => [user.id, other_user.id] +      }) +      |> json_response(200) + +    assert result["id"] == participation.id |> to_string + +    [participation] = Participation.for_user(user) +    participation = Repo.preload(participation, :recipients) + +    assert user in participation.recipients +    assert other_user in participation.recipients +  end + +  test "POST /api/v1/pleroma/conversations/read" do +    user = insert(:user) +    %{user: other_user, conn: conn} = oauth_access(["write:notifications"]) + +    {:ok, _activity} = +      CommonAPI.post(user, %{"status" => "Hi @#{other_user.nickname}", "visibility" => "direct"}) + +    {:ok, _activity} = +      CommonAPI.post(user, %{"status" => "Hi @#{other_user.nickname}", "visibility" => "direct"}) + +    [participation2, participation1] = Participation.for_user(other_user) +    assert Participation.get(participation2.id).read == false +    assert Participation.get(participation1.id).read == false +    assert User.get_cached_by_id(other_user.id).unread_conversation_count == 2 + +    [%{"unread" => false}, %{"unread" => false}] = +      conn +      |> post("/api/v1/pleroma/conversations/read", %{}) +      |> json_response(200) + +    [participation2, participation1] = Participation.for_user(other_user) +    assert Participation.get(participation2.id).read == true +    assert Participation.get(participation1.id).read == true +    assert User.get_cached_by_id(other_user.id).unread_conversation_count == 0 +  end + +  describe "POST /api/v1/pleroma/notifications/read" do +    setup do: oauth_access(["write:notifications"]) + +    test "it marks a single notification as read", %{user: user1, conn: conn} do +      user2 = insert(:user) +      {:ok, activity1} = CommonAPI.post(user2, %{"status" => "hi @#{user1.nickname}"}) +      {:ok, activity2} = CommonAPI.post(user2, %{"status" => "hi @#{user1.nickname}"}) +      {:ok, [notification1]} = Notification.create_notifications(activity1) +      {:ok, [notification2]} = Notification.create_notifications(activity2) + +      response = +        conn +        |> post("/api/v1/pleroma/notifications/read", %{"id" => "#{notification1.id}"}) +        |> json_response(:ok) + +      assert %{"pleroma" => %{"is_seen" => true}} = response +      assert Repo.get(Notification, notification1.id).seen +      refute Repo.get(Notification, notification2.id).seen +    end + +    test "it marks multiple notifications as read", %{user: user1, conn: conn} do +      user2 = insert(:user) +      {:ok, _activity1} = CommonAPI.post(user2, %{"status" => "hi @#{user1.nickname}"}) +      {:ok, _activity2} = CommonAPI.post(user2, %{"status" => "hi @#{user1.nickname}"}) +      {:ok, _activity3} = CommonAPI.post(user2, %{"status" => "HIE @#{user1.nickname}"}) + +      [notification3, notification2, notification1] = Notification.for_user(user1, %{limit: 3}) + +      [response1, response2] = +        conn +        |> post("/api/v1/pleroma/notifications/read", %{"max_id" => "#{notification2.id}"}) +        |> json_response(:ok) + +      assert %{"pleroma" => %{"is_seen" => true}} = response1 +      assert %{"pleroma" => %{"is_seen" => true}} = response2 +      assert Repo.get(Notification, notification1.id).seen +      assert Repo.get(Notification, notification2.id).seen +      refute Repo.get(Notification, notification3.id).seen +    end + +    test "it returns error when notification not found", %{conn: conn} do +      response = +        conn +        |> post("/api/v1/pleroma/notifications/read", %{"id" => "22222222222222"}) +        |> json_response(:bad_request) + +      assert response == %{"error" => "Cannot get notification"} +    end +  end +end diff --git a/test/web/pleroma_api/controllers/scrobble_controller_test.exs b/test/web/pleroma_api/controllers/scrobble_controller_test.exs new file mode 100644 index 000000000..2242610f1 --- /dev/null +++ b/test/web/pleroma_api/controllers/scrobble_controller_test.exs @@ -0,0 +1,58 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.PleromaAPI.ScrobbleControllerTest do +  use Pleroma.Web.ConnCase + +  alias Pleroma.Web.CommonAPI + +  describe "POST /api/v1/pleroma/scrobble" do +    test "works correctly" do +      %{conn: conn} = oauth_access(["write"]) + +      conn = +        post(conn, "/api/v1/pleroma/scrobble", %{ +          "title" => "lain radio episode 1", +          "artist" => "lain", +          "album" => "lain radio", +          "length" => "180000" +        }) + +      assert %{"title" => "lain radio episode 1"} = json_response(conn, 200) +    end +  end + +  describe "GET /api/v1/pleroma/accounts/:id/scrobbles" do +    test "works correctly" do +      %{user: user, conn: conn} = oauth_access(["read"]) + +      {:ok, _activity} = +        CommonAPI.listen(user, %{ +          "title" => "lain radio episode 1", +          "artist" => "lain", +          "album" => "lain radio" +        }) + +      {:ok, _activity} = +        CommonAPI.listen(user, %{ +          "title" => "lain radio episode 2", +          "artist" => "lain", +          "album" => "lain radio" +        }) + +      {:ok, _activity} = +        CommonAPI.listen(user, %{ +          "title" => "lain radio episode 3", +          "artist" => "lain", +          "album" => "lain radio" +        }) + +      conn = get(conn, "/api/v1/pleroma/accounts/#{user.id}/scrobbles") + +      result = json_response(conn, 200) + +      assert length(result) == 3 +    end +  end +end diff --git a/test/web/plugs/federating_plug_test.exs b/test/web/plugs/federating_plug_test.exs index c01e01124..9dcab93da 100644 --- a/test/web/plugs/federating_plug_test.exs +++ b/test/web/plugs/federating_plug_test.exs @@ -1,18 +1,10 @@  # Pleroma: A lightweight social networking server -# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.Web.FederatingPlugTest do    use Pleroma.Web.ConnCase - -  setup_all do -    config_path = [:instance, :federating] -    initial_setting = Pleroma.Config.get(config_path) - -    on_exit(fn -> Pleroma.Config.put(config_path, initial_setting) end) - -    :ok -  end +  clear_config_all([:instance, :federating])    test "returns and halt the conn when federating is disabled" do      Pleroma.Config.put([:instance, :federating], false) diff --git a/test/web/push/impl_test.exs b/test/web/push/impl_test.exs index 1e948086a..acae7a734 100644 --- a/test/web/push/impl_test.exs +++ b/test/web/push/impl_test.exs @@ -1,11 +1,12 @@  # Pleroma: A lightweight social networking server -# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.Web.Push.ImplTest do    use Pleroma.DataCase    alias Pleroma.Object +  alias Pleroma.User    alias Pleroma.Web.CommonAPI    alias Pleroma.Web.Push.Impl    alias Pleroma.Web.Push.Subscription @@ -84,7 +85,7 @@ defmodule Pleroma.Web.Push.ImplTest do             ) == :error    end -  test "delete subsciption if restult send message between 400..500" do +  test "delete subscription if result send message between 400..500" do      subscription = insert(:push_subscription)      assert Impl.push_message( @@ -97,7 +98,7 @@ defmodule Pleroma.Web.Push.ImplTest do      refute Pleroma.Repo.get(Subscription, subscription.id)    end -  test "renders body for create activity" do +  test "renders title and body for create activity" do      user = insert(:user, nickname: "Bob")      {:ok, activity} = @@ -116,19 +117,24 @@ defmodule Pleroma.Web.Push.ImplTest do               object             ) ==               "@Bob: Lorem ipsum dolor sit amet, consectetur  adipiscing elit. Fusce sagittis fini..." + +    assert Impl.format_title(%{activity: activity}) == +             "New Mention"    end -  test "renders body for follow activity" do +  test "renders title and body for follow activity" do      user = insert(:user, nickname: "Bob")      other_user = insert(:user)      {:ok, _, _, activity} = CommonAPI.follow(user, other_user)      object = Object.normalize(activity) -    assert Impl.format_body(%{activity: activity}, user, object) == -             "@Bob has followed you" +    assert Impl.format_body(%{activity: activity}, user, object) == "@Bob has followed you" + +    assert Impl.format_title(%{activity: activity}) == +             "New Follower"    end -  test "renders body for announce activity" do +  test "renders title and body for announce activity" do      user = insert(:user)      {:ok, activity} = @@ -142,9 +148,12 @@ defmodule Pleroma.Web.Push.ImplTest do      assert Impl.format_body(%{activity: announce_activity}, user, object) ==               "@#{user.nickname} repeated: Lorem ipsum dolor sit amet, consectetur  adipiscing elit. Fusce sagittis fini..." + +    assert Impl.format_title(%{activity: announce_activity}) == +             "New Repeat"    end -  test "renders body for like activity" do +  test "renders title and body for like activity" do      user = insert(:user, nickname: "Bob")      {:ok, activity} = @@ -156,7 +165,68 @@ defmodule Pleroma.Web.Push.ImplTest do      {:ok, activity, _} = CommonAPI.favorite(activity.id, user)      object = Object.normalize(activity) -    assert Impl.format_body(%{activity: activity}, user, object) == -             "@Bob has favorited your post" +    assert Impl.format_body(%{activity: activity}, user, object) == "@Bob has favorited your post" + +    assert Impl.format_title(%{activity: activity}) == +             "New Favorite" +  end + +  test "renders title for create activity with direct visibility" do +    user = insert(:user, nickname: "Bob") + +    {:ok, activity} = +      CommonAPI.post(user, %{ +        "visibility" => "direct", +        "status" => "This is just between you and me, pal" +      }) + +    assert Impl.format_title(%{activity: activity}) == +             "New Direct Message" +  end + +  describe "build_content/3" do +    test "returns info content for direct message with enabled privacy option" do +      user = insert(:user, nickname: "Bob") +      user2 = insert(:user, nickname: "Rob", notification_settings: %{privacy_option: true}) + +      {:ok, activity} = +        CommonAPI.post(user, %{ +          "visibility" => "direct", +          "status" => "<Lorem ipsum dolor sit amet." +        }) + +      notif = insert(:notification, user: user2, activity: activity) + +      actor = User.get_cached_by_ap_id(notif.activity.data["actor"]) +      object = Object.normalize(activity) + +      assert Impl.build_content(notif, actor, object) == %{ +               body: "@Bob", +               title: "New Direct Message" +             } +    end + +    test "returns regular content for direct message with disabled privacy option" do +      user = insert(:user, nickname: "Bob") +      user2 = insert(:user, nickname: "Rob", notification_settings: %{privacy_option: false}) + +      {:ok, activity} = +        CommonAPI.post(user, %{ +          "visibility" => "direct", +          "status" => +            "<span>Lorem ipsum dolor sit amet</span>, consectetur :firefox: adipiscing elit. Fusce sagittis finibus turpis." +        }) + +      notif = insert(:notification, user: user2, activity: activity) + +      actor = User.get_cached_by_ap_id(notif.activity.data["actor"]) +      object = Object.normalize(activity) + +      assert Impl.build_content(notif, actor, object) == %{ +               body: +                 "@Bob: Lorem ipsum dolor sit amet, consectetur  adipiscing elit. Fusce sagittis fini...", +               title: "New Direct Message" +             } +    end    end  end diff --git a/test/web/rel_me_test.exs b/test/web/rel_me_test.exs index 85515c432..77b5d5dc6 100644 --- a/test/web/rel_me_test.exs +++ b/test/web/rel_me_test.exs @@ -5,33 +5,8 @@  defmodule Pleroma.Web.RelMeTest do    use ExUnit.Case, async: true -  setup do -    Tesla.Mock.mock(fn -      %{ -        method: :get, -        url: "http://example.com/rel_me/anchor" -      } -> -        %Tesla.Env{status: 200, body: File.read!("test/fixtures/rel_me_anchor.html")} - -      %{ -        method: :get, -        url: "http://example.com/rel_me/anchor_nofollow" -      } -> -        %Tesla.Env{status: 200, body: File.read!("test/fixtures/rel_me_anchor_nofollow.html")} - -      %{ -        method: :get, -        url: "http://example.com/rel_me/link" -      } -> -        %Tesla.Env{status: 200, body: File.read!("test/fixtures/rel_me_link.html")} - -      %{ -        method: :get, -        url: "http://example.com/rel_me/null" -      } -> -        %Tesla.Env{status: 200, body: File.read!("test/fixtures/rel_me_null.html")} -    end) - +  setup_all do +    Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)      :ok    end @@ -39,7 +14,9 @@ defmodule Pleroma.Web.RelMeTest do      hrefs = ["https://social.example.org/users/lain"]      assert Pleroma.Web.RelMe.parse("http://example.com/rel_me/null") == {:ok, []} -    assert {:error, _} = Pleroma.Web.RelMe.parse("http://example.com/rel_me/error") + +    assert {:ok, %Tesla.Env{status: 404}} = +             Pleroma.Web.RelMe.parse("http://example.com/rel_me/error")      assert Pleroma.Web.RelMe.parse("http://example.com/rel_me/link") == {:ok, hrefs}      assert Pleroma.Web.RelMe.parse("http://example.com/rel_me/anchor") == {:ok, hrefs} diff --git a/test/web/retry_queue_test.exs b/test/web/retry_queue_test.exs deleted file mode 100644 index ecb3ce5d0..000000000 --- a/test/web/retry_queue_test.exs +++ /dev/null @@ -1,48 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/> -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule MockActivityPub do -  def publish_one({ret, waiter}) do -    send(waiter, :complete) -    {ret, "success"} -  end -end - -defmodule Pleroma.Web.Federator.RetryQueueTest do -  use Pleroma.DataCase -  alias Pleroma.Web.Federator.RetryQueue - -  @small_retry_count 0 -  @hopeless_retry_count 10 - -  setup do -    RetryQueue.reset_stats() -  end - -  test "RetryQueue responds to stats request" do -    assert %{delivered: 0, dropped: 0} == RetryQueue.get_stats() -  end - -  test "failed posts are retried" do -    {:retry, _timeout} = RetryQueue.get_retry_params(@small_retry_count) - -    wait_task = -      Task.async(fn -> -        receive do -          :complete -> :ok -        end -      end) - -    RetryQueue.enqueue({:ok, wait_task.pid}, MockActivityPub, @small_retry_count) -    Task.await(wait_task) -    assert %{delivered: 1, dropped: 0} == RetryQueue.get_stats() -  end - -  test "posts that have been tried too many times are dropped" do -    {:drop, _timeout} = RetryQueue.get_retry_params(@hopeless_retry_count) - -    RetryQueue.enqueue({:ok, nil}, MockActivityPub, @hopeless_retry_count) -    assert %{delivered: 0, dropped: 1} == RetryQueue.get_stats() -  end -end diff --git a/test/web/rich_media/aws_signed_url_test.exs b/test/web/rich_media/aws_signed_url_test.exs index 122787bc2..a3a50cbb1 100644 --- a/test/web/rich_media/aws_signed_url_test.exs +++ b/test/web/rich_media/aws_signed_url_test.exs @@ -60,7 +60,8 @@ defmodule Pleroma.Web.RichMedia.TTL.AwsSignedUrlTest do      {:ok, cache_ttl} = Cachex.ttl(:rich_media_cache, url)      # as there is delay in setting and pulling the data from cache we ignore 1 second -    assert_in_delta(valid_till * 1000, cache_ttl, 1000) +    # make it 2 seconds for flakyness +    assert_in_delta(valid_till * 1000, cache_ttl, 2000)    end    defp construct_s3_url(timestamp, valid_till) do diff --git a/test/web/rich_media/helpers_test.exs b/test/web/rich_media/helpers_test.exs index 92198f3d9..48884319d 100644 --- a/test/web/rich_media/helpers_test.exs +++ b/test/web/rich_media/helpers_test.exs @@ -15,12 +15,12 @@ defmodule Pleroma.Web.RichMedia.HelpersTest do    setup do      mock(fn env -> apply(HttpRequestMock, :request, [env]) end) -    rich_media = Config.get([:rich_media, :enabled]) -    on_exit(fn -> Config.put([:rich_media, :enabled], rich_media) end)      :ok    end +  clear_config([:rich_media, :enabled]) +    test "refuses to crawl incomplete URLs" do      user = insert(:user) diff --git a/test/web/rich_media/parser_test.exs b/test/web/rich_media/parser_test.exs index 19c19e895..b75bdf96f 100644 --- a/test/web/rich_media/parser_test.exs +++ b/test/web/rich_media/parser_test.exs @@ -59,7 +59,8 @@ defmodule Pleroma.Web.RichMedia.ParserTest do    test "doesn't just add a title" do      assert Pleroma.Web.RichMedia.Parser.parse("http://example.com/non-ogp") == -             {:error, "Found metadata was invalid or incomplete: %{}"} +             {:error, +              "Found metadata was invalid or incomplete: %{url: \"http://example.com/non-ogp\"}"}    end    test "parses ogp" do @@ -71,7 +72,7 @@ defmodule Pleroma.Web.RichMedia.ParserTest do                  description:                    "Directed by Michael Bay. With Sean Connery, Nicolas Cage, Ed Harris, John Spencer.",                  type: "video.movie", -                url: "http://www.imdb.com/title/tt0117500/" +                url: "http://example.com/ogp"                }}    end @@ -84,7 +85,7 @@ defmodule Pleroma.Web.RichMedia.ParserTest do                  description:                    "Directed by Michael Bay. With Sean Connery, Nicolas Cage, Ed Harris, John Spencer.",                  type: "video.movie", -                url: "http://www.imdb.com/title/tt0117500/" +                url: "http://example.com/ogp-missing-title"                }}    end @@ -96,7 +97,8 @@ defmodule Pleroma.Web.RichMedia.ParserTest do                  site: "@flickr",                  image: "https://farm6.staticflickr.com/5510/14338202952_93595258ff_z.jpg",                  title: "Small Island Developing States Photo Submission", -                description: "View the album on Flickr." +                description: "View the album on Flickr.", +                url: "http://example.com/twitter-card"                }}    end @@ -120,7 +122,7 @@ defmodule Pleroma.Web.RichMedia.ParserTest do                  thumbnail_width: 150,                  title: "Bacon Lollys",                  type: "photo", -                url: "https://farm4.staticflickr.com/3040/2362225867_4a87ab8baf_b.jpg", +                url: "http://example.com/oembed",                  version: "1.0",                  web_page: "https://www.flickr.com/photos/bees/2362225867/",                  web_page_short_url: "https://flic.kr/p/4AK2sc", diff --git a/test/web/rich_media/parsers/twitter_card_test.exs b/test/web/rich_media/parsers/twitter_card_test.exs new file mode 100644 index 000000000..f8e1c9b40 --- /dev/null +++ b/test/web/rich_media/parsers/twitter_card_test.exs @@ -0,0 +1,69 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.RichMedia.Parsers.TwitterCardTest do +  use ExUnit.Case, async: true +  alias Pleroma.Web.RichMedia.Parsers.TwitterCard + +  test "returns error when html not contains twitter card" do +    assert TwitterCard.parse("", %{}) == {:error, "No twitter card metadata found"} +  end + +  test "parses twitter card with only name attributes" do +    html = File.read!("test/fixtures/nypd-facial-recognition-children-teenagers3.html") + +    assert TwitterCard.parse(html, %{}) == +             {:ok, +              %{ +                "app:id:googleplay": "com.nytimes.android", +                "app:name:googleplay": "NYTimes", +                "app:url:googleplay": "nytimes://reader/id/100000006583622", +                site: nil, +                title: +                  "She Was Arrested at 14. Then Her Photo Went to a Facial Recognition Database. - The New York Times" +              }} +  end + +  test "parses twitter card with only property attributes" do +    html = File.read!("test/fixtures/nypd-facial-recognition-children-teenagers2.html") + +    assert TwitterCard.parse(html, %{}) == +             {:ok, +              %{ +                card: "summary_large_image", +                description: +                  "With little oversight, the N.Y.P.D. has been using powerful surveillance technology on photos of children and teenagers.", +                image: +                  "https://static01.nyt.com/images/2019/08/01/nyregion/01nypd-juveniles-promo/01nypd-juveniles-promo-videoSixteenByNineJumbo1600.jpg", +                "image:alt": "", +                title: +                  "She Was Arrested at 14. Then Her Photo Went to a Facial Recognition Database.", +                url: +                  "https://www.nytimes.com/2019/08/01/nyregion/nypd-facial-recognition-children-teenagers.html" +              }} +  end + +  test "parses twitter card with name & property attributes" do +    html = File.read!("test/fixtures/nypd-facial-recognition-children-teenagers.html") + +    assert TwitterCard.parse(html, %{}) == +             {:ok, +              %{ +                "app:id:googleplay": "com.nytimes.android", +                "app:name:googleplay": "NYTimes", +                "app:url:googleplay": "nytimes://reader/id/100000006583622", +                card: "summary_large_image", +                description: +                  "With little oversight, the N.Y.P.D. has been using powerful surveillance technology on photos of children and teenagers.", +                image: +                  "https://static01.nyt.com/images/2019/08/01/nyregion/01nypd-juveniles-promo/01nypd-juveniles-promo-videoSixteenByNineJumbo1600.jpg", +                "image:alt": "", +                site: nil, +                title: +                  "She Was Arrested at 14. Then Her Photo Went to a Facial Recognition Database.", +                url: +                  "https://www.nytimes.com/2019/08/01/nyregion/nypd-facial-recognition-children-teenagers.html" +              }} +  end +end diff --git a/test/web/salmon/salmon_test.exs b/test/web/salmon/salmon_test.exs deleted file mode 100644 index e86e76fe9..000000000 --- a/test/web/salmon/salmon_test.exs +++ /dev/null @@ -1,101 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/> -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Web.Salmon.SalmonTest do -  use Pleroma.DataCase -  alias Pleroma.Activity -  alias Pleroma.Keys -  alias Pleroma.Repo -  alias Pleroma.User -  alias Pleroma.Web.Federator.Publisher -  alias Pleroma.Web.Salmon -  import Mock -  import Pleroma.Factory - -  @magickey "RSA.pu0s-halox4tu7wmES1FVSx6u-4wc0YrUFXcqWXZG4-27UmbCOpMQftRCldNRfyA-qLbz-eqiwQhh-1EwUvjsD4cYbAHNGHwTvDOyx5AKthQUP44ykPv7kjKGh3DWKySJvcs9tlUG87hlo7AvnMo9pwRS_Zz2CacQ-MKaXyDepk=.AQAB" - -  @wrong_magickey "RSA.pu0s-halox4tu7wmES1FVSx6u-4wc0YrUFXcqWXZG4-27UmbCOpMQftRCldNRfyA-qLbz-eqiwQhh-1EwUvjsD4cYbAHNGHwTvDOyx5AKthQUP44ykPv7kjKGh3DWKySJvcs9tlUG87hlo7AvnMo9pwRS_Zz2CacQ-MKaXyDepk=.AQAA" - -  @magickey_friendica "RSA.AMwa8FUs2fWEjX0xN7yRQgegQffhBpuKNC6fa5VNSVorFjGZhRrlPMn7TQOeihlc9lBz2OsHlIedbYn2uJ7yCs0.AQAB" - -  setup_all do -    Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end) -    :ok -  end - -  test "decodes a salmon" do -    {:ok, salmon} = File.read("test/fixtures/salmon.xml") -    {:ok, doc} = Salmon.decode_and_validate(@magickey, salmon) -    assert Regex.match?(~r/xml/, doc) -  end - -  test "errors on wrong magic key" do -    {:ok, salmon} = File.read("test/fixtures/salmon.xml") -    assert Salmon.decode_and_validate(@wrong_magickey, salmon) == :error -  end - -  test "it encodes a magic key from a public key" do -    key = Salmon.decode_key(@magickey) -    magic_key = Salmon.encode_key(key) - -    assert @magickey == magic_key -  end - -  test "it decodes a friendica public key" do -    _key = Salmon.decode_key(@magickey_friendica) -  end - -  test "encodes an xml payload with a private key" do -    doc = File.read!("test/fixtures/incoming_note_activity.xml") -    pem = File.read!("test/fixtures/private_key.pem") -    {:ok, private, public} = Keys.keys_from_pem(pem) - -    # Let's try a roundtrip. -    {:ok, salmon} = Salmon.encode(private, doc) -    {:ok, decoded_doc} = Salmon.decode_and_validate(Salmon.encode_key(public), salmon) - -    assert doc == decoded_doc -  end - -  test "it gets a magic key" do -    salmon = File.read!("test/fixtures/salmon2.xml") -    {:ok, key} = Salmon.fetch_magic_key(salmon) - -    assert key == -             "RSA.uzg6r1peZU0vXGADWxGJ0PE34WvmhjUmydbX5YYdOiXfODVLwCMi1umGoqUDm-mRu4vNEdFBVJU1CpFA7dKzWgIsqsa501i2XqElmEveXRLvNRWFB6nG03Q5OUY2as8eE54BJm0p20GkMfIJGwP6TSFb-ICp3QjzbatuSPJ6xCE=.AQAB" -  end - -  test_with_mock "it pushes an activity to remote accounts it's addressed to", -                 Publisher, -                 [:passthrough], -                 [] do -    user_data = %{ -      info: %{ -        salmon: "http://test-example.org/salmon" -      }, -      local: false -    } - -    mentioned_user = insert(:user, user_data) -    note = insert(:note) - -    activity_data = %{ -      "id" => Pleroma.Web.ActivityPub.Utils.generate_activity_id(), -      "type" => "Create", -      "actor" => note.data["actor"], -      "to" => note.data["to"] ++ [mentioned_user.ap_id], -      "object" => note.data, -      "published_at" => DateTime.utc_now() |> DateTime.to_iso8601(), -      "context" => note.data["context"] -    } - -    {:ok, activity} = Repo.insert(%Activity{data: activity_data, recipients: activity_data["to"]}) -    user = User.get_cached_by_ap_id(activity.data["actor"]) -    {:ok, user} = User.ensure_keys_present(user) - -    Salmon.publish(user, activity) - -    assert called(Publisher.enqueue_one(Salmon, %{recipient: mentioned_user})) -  end -end diff --git a/test/web/static_fe/static_fe_controller_test.exs b/test/web/static_fe/static_fe_controller_test.exs new file mode 100644 index 000000000..2ce8f9fa3 --- /dev/null +++ b/test/web/static_fe/static_fe_controller_test.exs @@ -0,0 +1,210 @@ +defmodule Pleroma.Web.StaticFE.StaticFEControllerTest do +  use Pleroma.Web.ConnCase +  alias Pleroma.Activity +  alias Pleroma.Web.ActivityPub.Transmogrifier +  alias Pleroma.Web.CommonAPI + +  import Pleroma.Factory + +  clear_config_all([:static_fe, :enabled]) do +    Pleroma.Config.put([:static_fe, :enabled], true) +  end + +  describe "user profile page" do +    test "just the profile as HTML", %{conn: conn} do +      user = insert(:user) + +      conn = +        conn +        |> put_req_header("accept", "text/html") +        |> get("/users/#{user.nickname}") + +      assert html_response(conn, 200) =~ user.nickname +    end + +    test "renders json unless there's an html accept header", %{conn: conn} do +      user = insert(:user) + +      conn = +        conn +        |> put_req_header("accept", "application/json") +        |> get("/users/#{user.nickname}") + +      assert json_response(conn, 200) +    end + +    test "404 when user not found", %{conn: conn} do +      conn = +        conn +        |> put_req_header("accept", "text/html") +        |> get("/users/limpopo") + +      assert html_response(conn, 404) =~ "not found" +    end + +    test "profile does not include private messages", %{conn: conn} do +      user = insert(:user) +      CommonAPI.post(user, %{"status" => "public"}) +      CommonAPI.post(user, %{"status" => "private", "visibility" => "private"}) + +      conn = +        conn +        |> put_req_header("accept", "text/html") +        |> get("/users/#{user.nickname}") + +      html = html_response(conn, 200) + +      assert html =~ ">public<" +      refute html =~ ">private<" +    end + +    test "pagination", %{conn: conn} do +      user = insert(:user) +      Enum.map(1..30, fn i -> CommonAPI.post(user, %{"status" => "test#{i}"}) end) + +      conn = +        conn +        |> put_req_header("accept", "text/html") +        |> get("/users/#{user.nickname}") + +      html = html_response(conn, 200) + +      assert html =~ ">test30<" +      assert html =~ ">test11<" +      refute html =~ ">test10<" +      refute html =~ ">test1<" +    end + +    test "pagination, page 2", %{conn: conn} do +      user = insert(:user) +      activities = Enum.map(1..30, fn i -> CommonAPI.post(user, %{"status" => "test#{i}"}) end) +      {:ok, a11} = Enum.at(activities, 11) + +      conn = +        conn +        |> put_req_header("accept", "text/html") +        |> get("/users/#{user.nickname}?max_id=#{a11.id}") + +      html = html_response(conn, 200) + +      assert html =~ ">test1<" +      assert html =~ ">test10<" +      refute html =~ ">test20<" +      refute html =~ ">test29<" +    end +  end + +  describe "notice rendering" do +    test "single notice page", %{conn: conn} do +      user = insert(:user) +      {:ok, activity} = CommonAPI.post(user, %{"status" => "testing a thing!"}) + +      conn = +        conn +        |> put_req_header("accept", "text/html") +        |> get("/notice/#{activity.id}") + +      html = html_response(conn, 200) +      assert html =~ "<header>" +      assert html =~ user.nickname +      assert html =~ "testing a thing!" +    end + +    test "shows the whole thread", %{conn: conn} do +      user = insert(:user) +      {:ok, activity} = CommonAPI.post(user, %{"status" => "space: the final frontier"}) + +      CommonAPI.post(user, %{ +        "status" => "these are the voyages or something", +        "in_reply_to_status_id" => activity.id +      }) + +      conn = +        conn +        |> put_req_header("accept", "text/html") +        |> get("/notice/#{activity.id}") + +      html = html_response(conn, 200) +      assert html =~ "the final frontier" +      assert html =~ "voyages" +    end + +    test "redirect by AP object ID", %{conn: conn} do +      user = insert(:user) + +      {:ok, %Activity{data: %{"object" => object_url}}} = +        CommonAPI.post(user, %{"status" => "beam me up"}) + +      conn = +        conn +        |> put_req_header("accept", "text/html") +        |> get(URI.parse(object_url).path) + +      assert html_response(conn, 302) =~ "redirected" +    end + +    test "redirect by activity ID", %{conn: conn} do +      user = insert(:user) + +      {:ok, %Activity{data: %{"id" => id}}} = +        CommonAPI.post(user, %{"status" => "I'm a doctor, not a devops!"}) + +      conn = +        conn +        |> put_req_header("accept", "text/html") +        |> get(URI.parse(id).path) + +      assert html_response(conn, 302) =~ "redirected" +    end + +    test "404 when notice not found", %{conn: conn} do +      conn = +        conn +        |> put_req_header("accept", "text/html") +        |> get("/notice/88c9c317") + +      assert html_response(conn, 404) =~ "not found" +    end + +    test "404 for private status", %{conn: conn} do +      user = insert(:user) + +      {:ok, activity} = +        CommonAPI.post(user, %{"status" => "don't show me!", "visibility" => "private"}) + +      conn = +        conn +        |> put_req_header("accept", "text/html") +        |> get("/notice/#{activity.id}") + +      assert html_response(conn, 404) =~ "not found" +    end + +    test "302 for remote cached status", %{conn: conn} do +      user = insert(:user) + +      message = %{ +        "@context" => "https://www.w3.org/ns/activitystreams", +        "to" => user.follower_address, +        "cc" => "https://www.w3.org/ns/activitystreams#Public", +        "type" => "Create", +        "object" => %{ +          "content" => "blah blah blah", +          "type" => "Note", +          "attributedTo" => user.ap_id, +          "inReplyTo" => nil +        }, +        "actor" => user.ap_id +      } + +      assert {:ok, activity} = Transmogrifier.handle_incoming(message) + +      conn = +        conn +        |> put_req_header("accept", "text/html") +        |> get("/notice/#{activity.id}") + +      assert html_response(conn, 302) =~ "redirected" +    end +  end +end diff --git a/test/web/streamer/ping_test.exs b/test/web/streamer/ping_test.exs new file mode 100644 index 000000000..3d52c00e4 --- /dev/null +++ b/test/web/streamer/ping_test.exs @@ -0,0 +1,36 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.PingTest do +  use Pleroma.DataCase + +  import Pleroma.Factory +  alias Pleroma.Web.Streamer + +  setup do +    start_supervised({Streamer.supervisor(), [ping_interval: 30]}) + +    :ok +  end + +  describe "sockets" do +    setup do +      user = insert(:user) +      {:ok, %{user: user}} +    end + +    test "it sends pings", %{user: user} do +      task = +        Task.async(fn -> +          assert_receive {:text, received_event}, 40 +          assert_receive {:text, received_event}, 40 +          assert_receive {:text, received_event}, 40 +        end) + +      Streamer.add_socket("public", %{transport_pid: task.pid, assigns: %{user: user}}) + +      Task.await(task) +    end +  end +end diff --git a/test/web/streamer/state_test.exs b/test/web/streamer/state_test.exs new file mode 100644 index 000000000..d1aeac541 --- /dev/null +++ b/test/web/streamer/state_test.exs @@ -0,0 +1,54 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.StateTest do +  use Pleroma.DataCase + +  import Pleroma.Factory +  alias Pleroma.Web.Streamer +  alias Pleroma.Web.Streamer.StreamerSocket + +  @moduletag needs_streamer: true + +  describe "sockets" do +    setup do +      user = insert(:user) +      user2 = insert(:user) +      {:ok, %{user: user, user2: user2}} +    end + +    test "it can add a socket", %{user: user} do +      Streamer.add_socket("public", %{transport_pid: 1, assigns: %{user: user}}) + +      assert(%{"public" => [%StreamerSocket{transport_pid: 1}]} = Streamer.get_sockets()) +    end + +    test "it can add multiple sockets per user", %{user: user} do +      Streamer.add_socket("public", %{transport_pid: 1, assigns: %{user: user}}) +      Streamer.add_socket("public", %{transport_pid: 2, assigns: %{user: user}}) + +      assert( +        %{ +          "public" => [ +            %StreamerSocket{transport_pid: 2}, +            %StreamerSocket{transport_pid: 1} +          ] +        } = Streamer.get_sockets() +      ) +    end + +    test "it will not add a duplicate socket", %{user: user} do +      Streamer.add_socket("activity", %{transport_pid: 1, assigns: %{user: user}}) +      Streamer.add_socket("activity", %{transport_pid: 1, assigns: %{user: user}}) + +      assert( +        %{ +          "activity" => [ +            %StreamerSocket{transport_pid: 1} +          ] +        } = Streamer.get_sockets() +      ) +    end +  end +end diff --git a/test/web/streamer_test.exs b/test/web/streamer/streamer_test.exs index 4633d7765..7166d6f0b 100644 --- a/test/web/streamer_test.exs +++ b/test/web/streamer/streamer_test.exs @@ -1,36 +1,29 @@  # Pleroma: A lightweight social networking server -# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.Web.StreamerTest do    use Pleroma.DataCase +  import Pleroma.Factory + +  alias Pleroma.Conversation.Participation    alias Pleroma.List    alias Pleroma.User    alias Pleroma.Web.CommonAPI    alias Pleroma.Web.Streamer -  import Pleroma.Factory +  alias Pleroma.Web.Streamer.StreamerSocket +  alias Pleroma.Web.Streamer.Worker -  setup do -    skip_thread_containment = Pleroma.Config.get([:instance, :skip_thread_containment]) +  @moduletag needs_streamer: true, capture_log: true -    on_exit(fn -> -      Pleroma.Config.put([:instance, :skip_thread_containment], skip_thread_containment) -    end) +  @streamer_timeout 150 +  @streamer_start_wait 10 -    :ok -  end +  clear_config_all([:instance, :skip_thread_containment])    describe "user streams" do      setup do -      GenServer.start(Streamer, %{}, name: Streamer) - -      on_exit(fn -> -        if pid = Process.whereis(Streamer) do -          Process.exit(pid, :kill) -        end -      end) -        user = insert(:user)        notify = insert(:notification, user: user, activity: build(:note_activity))        {:ok, %{user: user, notify: notify}} @@ -39,7 +32,7 @@ defmodule Pleroma.Web.StreamerTest do      test "it sends notify to in the 'user' stream", %{user: user, notify: notify} do        task =          Task.async(fn -> -          assert_receive {:text, _}, 4_000 +          assert_receive {:text, _}, @streamer_timeout          end)        Streamer.add_socket( @@ -54,7 +47,7 @@ defmodule Pleroma.Web.StreamerTest do      test "it sends notify to in the 'user:notification' stream", %{user: user, notify: notify} do        task =          Task.async(fn -> -          assert_receive {:text, _}, 4_000 +          assert_receive {:text, _}, @streamer_timeout          end)        Streamer.add_socket( @@ -65,6 +58,83 @@ defmodule Pleroma.Web.StreamerTest do        Streamer.stream("user:notification", notify)        Task.await(task)      end + +    test "it doesn't send notify to the 'user:notification' stream when a user is blocked", %{ +      user: user +    } do +      blocked = insert(:user) +      {:ok, _user_relationship} = User.block(user, blocked) + +      task = Task.async(fn -> refute_receive {:text, _}, @streamer_timeout end) + +      Streamer.add_socket( +        "user:notification", +        %{transport_pid: task.pid, assigns: %{user: user}} +      ) + +      {:ok, activity} = CommonAPI.post(user, %{"status" => ":("}) +      {:ok, notif, _} = CommonAPI.favorite(activity.id, blocked) + +      Streamer.stream("user:notification", notif) +      Task.await(task) +    end + +    test "it doesn't send notify to the 'user:notification' stream when a thread is muted", %{ +      user: user +    } do +      user2 = insert(:user) +      task = Task.async(fn -> refute_receive {:text, _}, @streamer_timeout end) + +      Streamer.add_socket( +        "user:notification", +        %{transport_pid: task.pid, assigns: %{user: user}} +      ) + +      {:ok, activity} = CommonAPI.post(user, %{"status" => "super hot take"}) +      {:ok, activity} = CommonAPI.add_mute(user, activity) +      {:ok, notif, _} = CommonAPI.favorite(activity.id, user2) +      Streamer.stream("user:notification", notif) +      Task.await(task) +    end + +    test "it doesn't send notify to the 'user:notification' stream' when a domain is blocked", %{ +      user: user +    } do +      user2 = insert(:user, %{ap_id: "https://hecking-lewd-place.com/user/meanie"}) +      task = Task.async(fn -> refute_receive {:text, _}, @streamer_timeout end) + +      Streamer.add_socket( +        "user:notification", +        %{transport_pid: task.pid, assigns: %{user: user}} +      ) + +      {:ok, user} = User.block_domain(user, "hecking-lewd-place.com") +      {:ok, activity} = CommonAPI.post(user, %{"status" => "super hot take"}) +      {:ok, notif, _} = CommonAPI.favorite(activity.id, user2) + +      Streamer.stream("user:notification", notif) +      Task.await(task) +    end + +    test "it sends follow activities to the 'user:notification' stream", %{ +      user: user +    } do +      user2 = insert(:user) +      task = Task.async(fn -> assert_receive {:text, _}, @streamer_timeout end) + +      Process.sleep(@streamer_start_wait) + +      Streamer.add_socket( +        "user:notification", +        %{transport_pid: task.pid, assigns: %{user: user}} +      ) + +      {:ok, _follower, _followed, _activity} = CommonAPI.follow(user2, user) + +      # We don't directly pipe the notification to the streamer as it's already +      # generated as a side effect of CommonAPI.follow(). +      Task.await(task) +    end    end    test "it sends to public" do @@ -73,14 +143,12 @@ defmodule Pleroma.Web.StreamerTest do      task =        Task.async(fn -> -        assert_receive {:text, _}, 4_000 +        assert_receive {:text, _}, @streamer_timeout        end) -    fake_socket = %{ +    fake_socket = %StreamerSocket{        transport_pid: task.pid, -      assigns: %{ -        user: user -      } +      user: user      }      {:ok, activity} = CommonAPI.post(other_user, %{"status" => "Test"}) @@ -89,7 +157,7 @@ defmodule Pleroma.Web.StreamerTest do        "public" => [fake_socket]      } -    Streamer.push_to_socket(topics, "public", activity) +    Worker.push_to_socket(topics, "public", activity)      Task.await(task) @@ -102,15 +170,13 @@ defmodule Pleroma.Web.StreamerTest do            }            |> Jason.encode!() -        assert_receive {:text, received_event}, 4_000 +        assert_receive {:text, received_event}, @streamer_timeout          assert received_event == expected_event        end) -    fake_socket = %{ +    fake_socket = %StreamerSocket{        transport_pid: task.pid, -      assigns: %{ -        user: user -      } +      user: user      }      {:ok, activity} = CommonAPI.delete(activity.id, other_user) @@ -119,7 +185,7 @@ defmodule Pleroma.Web.StreamerTest do        "public" => [fake_socket]      } -    Streamer.push_to_socket(topics, "public", activity) +    Worker.push_to_socket(topics, "public", activity)      Task.await(task)    end @@ -128,7 +194,8 @@ defmodule Pleroma.Web.StreamerTest do      test "it doesn't send to user if recipients invalid and thread containment is enabled" do        Pleroma.Config.put([:instance, :skip_thread_containment], false)        author = insert(:user) -      user = insert(:user, following: [author.ap_id]) +      user = insert(:user) +      User.follow(user, author, "accept")        activity =          insert(:note_activity, @@ -140,9 +207,9 @@ defmodule Pleroma.Web.StreamerTest do          )        task = Task.async(fn -> refute_receive {:text, _}, 1_000 end) -      fake_socket = %{transport_pid: task.pid, assigns: %{user: user}} +      fake_socket = %StreamerSocket{transport_pid: task.pid, user: user}        topics = %{"public" => [fake_socket]} -      Streamer.push_to_socket(topics, "public", activity) +      Worker.push_to_socket(topics, "public", activity)        Task.await(task)      end @@ -150,7 +217,8 @@ defmodule Pleroma.Web.StreamerTest do      test "it sends message if recipients invalid and thread containment is disabled" do        Pleroma.Config.put([:instance, :skip_thread_containment], true)        author = insert(:user) -      user = insert(:user, following: [author.ap_id]) +      user = insert(:user) +      User.follow(user, author, "accept")        activity =          insert(:note_activity, @@ -162,9 +230,9 @@ defmodule Pleroma.Web.StreamerTest do          )        task = Task.async(fn -> assert_receive {:text, _}, 1_000 end) -      fake_socket = %{transport_pid: task.pid, assigns: %{user: user}} +      fake_socket = %StreamerSocket{transport_pid: task.pid, user: user}        topics = %{"public" => [fake_socket]} -      Streamer.push_to_socket(topics, "public", activity) +      Worker.push_to_socket(topics, "public", activity)        Task.await(task)      end @@ -172,7 +240,8 @@ defmodule Pleroma.Web.StreamerTest do      test "it sends message if recipients invalid and thread containment is enabled but user's thread containment is disabled" do        Pleroma.Config.put([:instance, :skip_thread_containment], false)        author = insert(:user) -      user = insert(:user, following: [author.ap_id], info: %{skip_thread_containment: true}) +      user = insert(:user, skip_thread_containment: true) +      User.follow(user, author, "accept")        activity =          insert(:note_activity, @@ -184,40 +253,76 @@ defmodule Pleroma.Web.StreamerTest do          )        task = Task.async(fn -> assert_receive {:text, _}, 1_000 end) -      fake_socket = %{transport_pid: task.pid, assigns: %{user: user}} +      fake_socket = %StreamerSocket{transport_pid: task.pid, user: user}        topics = %{"public" => [fake_socket]} -      Streamer.push_to_socket(topics, "public", activity) +      Worker.push_to_socket(topics, "public", activity)        Task.await(task)      end    end -  test "it doesn't send to blocked users" do -    user = insert(:user) -    blocked_user = insert(:user) -    {:ok, user} = User.block(user, blocked_user) +  describe "blocks" do +    test "it doesn't send messages involving blocked users" do +      user = insert(:user) +      blocked_user = insert(:user) +      {:ok, _user_relationship} = User.block(user, blocked_user) -    task = -      Task.async(fn -> -        refute_receive {:text, _}, 1_000 -      end) +      task = +        Task.async(fn -> +          refute_receive {:text, _}, 1_000 +        end) -    fake_socket = %{ -      transport_pid: task.pid, -      assigns: %{ +      fake_socket = %StreamerSocket{ +        transport_pid: task.pid,          user: user        } -    } -    {:ok, activity} = CommonAPI.post(blocked_user, %{"status" => "Test"}) +      {:ok, activity} = CommonAPI.post(blocked_user, %{"status" => "Test"}) -    topics = %{ -      "public" => [fake_socket] -    } +      topics = %{ +        "public" => [fake_socket] +      } -    Streamer.push_to_socket(topics, "public", activity) +      Worker.push_to_socket(topics, "public", activity) -    Task.await(task) +      Task.await(task) +    end + +    test "it doesn't send messages transitively involving blocked users" do +      blocker = insert(:user) +      blockee = insert(:user) +      friend = insert(:user) + +      task = +        Task.async(fn -> +          refute_receive {:text, _}, 1_000 +        end) + +      fake_socket = %StreamerSocket{ +        transport_pid: task.pid, +        user: blocker +      } + +      topics = %{ +        "public" => [fake_socket] +      } + +      {:ok, _user_relationship} = User.block(blocker, blockee) + +      {:ok, activity_one} = CommonAPI.post(friend, %{"status" => "hey! @#{blockee.nickname}"}) + +      Worker.push_to_socket(topics, "public", activity_one) + +      {:ok, activity_two} = CommonAPI.post(blockee, %{"status" => "hey! @#{friend.nickname}"}) + +      Worker.push_to_socket(topics, "public", activity_two) + +      {:ok, activity_three} = CommonAPI.post(blockee, %{"status" => "hey! @#{blocker.nickname}"}) + +      Worker.push_to_socket(topics, "public", activity_three) + +      Task.await(task) +    end    end    test "it doesn't send unwanted DMs to list" do @@ -235,11 +340,9 @@ defmodule Pleroma.Web.StreamerTest do          refute_receive {:text, _}, 1_000        end) -    fake_socket = %{ +    fake_socket = %StreamerSocket{        transport_pid: task.pid, -      assigns: %{ -        user: user_a -      } +      user: user_a      }      {:ok, activity} = @@ -252,7 +355,7 @@ defmodule Pleroma.Web.StreamerTest do        "list:#{list.id}" => [fake_socket]      } -    Streamer.handle_cast(%{action: :stream, topic: "list", item: activity}, topics) +    Worker.handle_call({:stream, "list", activity}, self(), topics)      Task.await(task)    end @@ -269,11 +372,9 @@ defmodule Pleroma.Web.StreamerTest do          refute_receive {:text, _}, 1_000        end) -    fake_socket = %{ +    fake_socket = %StreamerSocket{        transport_pid: task.pid, -      assigns: %{ -        user: user_a -      } +      user: user_a      }      {:ok, activity} = @@ -286,12 +387,12 @@ defmodule Pleroma.Web.StreamerTest do        "list:#{list.id}" => [fake_socket]      } -    Streamer.handle_cast(%{action: :stream, topic: "list", item: activity}, topics) +    Worker.handle_call({:stream, "list", activity}, self(), topics)      Task.await(task)    end -  test "it send wanted private posts to list" do +  test "it sends wanted private posts to list" do      user_a = insert(:user)      user_b = insert(:user) @@ -305,11 +406,9 @@ defmodule Pleroma.Web.StreamerTest do          assert_receive {:text, _}, 1_000        end) -    fake_socket = %{ +    fake_socket = %StreamerSocket{        transport_pid: task.pid, -      assigns: %{ -        user: user_a -      } +      user: user_a      }      {:ok, activity} = @@ -318,11 +417,12 @@ defmodule Pleroma.Web.StreamerTest do          "visibility" => "private"        }) -    topics = %{ -      "list:#{list.id}" => [fake_socket] -    } +    Streamer.add_socket( +      "list:#{list.id}", +      fake_socket +    ) -    Streamer.handle_cast(%{action: :stream, topic: "list", item: activity}, topics) +    Worker.handle_call({:stream, "list", activity}, self(), %{})      Task.await(task)    end @@ -338,11 +438,9 @@ defmodule Pleroma.Web.StreamerTest do          refute_receive {:text, _}, 1_000        end) -    fake_socket = %{ +    fake_socket = %StreamerSocket{        transport_pid: task.pid, -      assigns: %{ -        user: user1 -      } +      user: user1      }      {:ok, create_activity} = CommonAPI.post(user3, %{"status" => "I'm kawen"}) @@ -352,21 +450,33 @@ defmodule Pleroma.Web.StreamerTest do        "public" => [fake_socket]      } -    Streamer.push_to_socket(topics, "public", announce_activity) +    Worker.push_to_socket(topics, "public", announce_activity)      Task.await(task)    end -  describe "direct streams" do -    setup do -      GenServer.start(Streamer, %{}, name: Streamer) +  test "it doesn't send posts from muted threads" do +    user = insert(:user) +    user2 = insert(:user) +    {:ok, user2, user, _activity} = CommonAPI.follow(user2, user) -      on_exit(fn -> -        if pid = Process.whereis(Streamer) do -          Process.exit(pid, :kill) -        end -      end) +    {:ok, activity} = CommonAPI.post(user, %{"status" => "super hot take"}) + +    {:ok, activity} = CommonAPI.add_mute(user2, activity) +    task = Task.async(fn -> refute_receive {:text, _}, @streamer_timeout end) + +    Streamer.add_socket( +      "user", +      %{transport_pid: task.pid, assigns: %{user: user2}} +    ) + +    Streamer.stream("user", activity) +    Task.await(task) +  end + +  describe "direct streams" do +    setup do        :ok      end @@ -376,7 +486,14 @@ defmodule Pleroma.Web.StreamerTest do        task =          Task.async(fn -> -          assert_receive {:text, _received_event}, 4_000 +          assert_receive {:text, received_event}, @streamer_timeout + +          assert %{"event" => "conversation", "payload" => received_payload} = +                   Jason.decode!(received_event) + +          assert %{"last_status" => last_status} = Jason.decode!(received_payload) +          [participation] = Participation.for_user(user) +          assert last_status["pleroma"]["direct_conversation_id"] == participation.id          end)        Streamer.add_socket( @@ -393,7 +510,7 @@ defmodule Pleroma.Web.StreamerTest do        Task.await(task)      end -    test "it doesn't send conversation update to the 'direct' streamj when the last message in the conversation is deleted" do +    test "it doesn't send conversation update to the 'direct' stream when the last message in the conversation is deleted" do        user = insert(:user)        another_user = insert(:user) @@ -405,12 +522,14 @@ defmodule Pleroma.Web.StreamerTest do        task =          Task.async(fn -> -          assert_receive {:text, received_event}, 4_000 +          assert_receive {:text, received_event}, @streamer_timeout            assert %{"event" => "delete", "payload" => _} = Jason.decode!(received_event) -          refute_receive {:text, _}, 4_000 +          refute_receive {:text, _}, @streamer_timeout          end) +      Process.sleep(@streamer_start_wait) +        Streamer.add_socket(          "direct",          %{transport_pid: task.pid, assigns: %{user: user}} @@ -440,10 +559,10 @@ defmodule Pleroma.Web.StreamerTest do        task =          Task.async(fn -> -          assert_receive {:text, received_event}, 4_000 +          assert_receive {:text, received_event}, @streamer_timeout            assert %{"event" => "delete", "payload" => _} = Jason.decode!(received_event) -          assert_receive {:text, received_event}, 4_000 +          assert_receive {:text, received_event}, @streamer_timeout            assert %{"event" => "conversation", "payload" => received_payload} =                     Jason.decode!(received_event) @@ -452,6 +571,8 @@ defmodule Pleroma.Web.StreamerTest do            assert last_status["id"] == to_string(create_activity.id)          end) +      Process.sleep(@streamer_start_wait) +        Streamer.add_socket(          "direct",          %{transport_pid: task.pid, assigns: %{user: user}} diff --git a/test/web/twitter_api/password_controller_test.exs b/test/web/twitter_api/password_controller_test.exs index 3a7246ea8..29ba7d265 100644 --- a/test/web/twitter_api/password_controller_test.exs +++ b/test/web/twitter_api/password_controller_test.exs @@ -6,6 +6,7 @@ defmodule Pleroma.Web.TwitterAPI.PasswordControllerTest do    use Pleroma.Web.ConnCase    alias Pleroma.PasswordResetToken +  alias Pleroma.User    alias Pleroma.Web.OAuth.Token    import Pleroma.Factory @@ -54,7 +55,27 @@ defmodule Pleroma.Web.TwitterAPI.PasswordControllerTest do        user = refresh_record(user)        assert Comeonin.Pbkdf2.checkpw("test", user.password_hash) -      assert length(Token.get_user_tokens(user)) == 0 +      assert Enum.empty?(Token.get_user_tokens(user)) +    end + +    test "it sets password_reset_pending to false", %{conn: conn} do +      user = insert(:user, password_reset_pending: true) + +      {:ok, token} = PasswordResetToken.create_token(user) +      {:ok, _access_token} = Token.create_token(insert(:oauth_app), user, %{}) + +      params = %{ +        "password" => "test", +        password_confirmation: "test", +        token: token.token +      } + +      conn +      |> assign(:user, user) +      |> post("/api/pleroma/password_reset", %{data: params}) +      |> html_response(:ok) + +      assert User.get_by_id(user.id).password_reset_pending == false      end    end  end diff --git a/test/web/twitter_api/remote_follow_controller_test.exs b/test/web/twitter_api/remote_follow_controller_test.exs new file mode 100644 index 000000000..444949375 --- /dev/null +++ b/test/web/twitter_api/remote_follow_controller_test.exs @@ -0,0 +1,235 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.TwitterAPI.RemoteFollowControllerTest do +  use Pleroma.Web.ConnCase + +  alias Pleroma.User +  alias Pleroma.Web.CommonAPI +  import ExUnit.CaptureLog +  import Pleroma.Factory + +  setup do +    Tesla.Mock.mock(fn env -> apply(HttpRequestMock, :request, [env]) end) +    :ok +  end + +  clear_config([:instance]) +  clear_config([:frontend_configurations, :pleroma_fe]) +  clear_config([:user, :deny_follow_blocked]) + +  describe "GET /ostatus_subscribe - remote_follow/2" do +    test "adds status to pleroma instance if the `acct` is a status", %{conn: conn} do +      assert conn +             |> get( +               remote_follow_path(conn, :follow, %{ +                 acct: "https://mastodon.social/users/emelie/statuses/101849165031453009" +               }) +             ) +             |> redirected_to() =~ "/notice/" +    end + +    test "show follow account page if the `acct` is a account link", %{conn: conn} do +      response = +        conn +        |> get(remote_follow_path(conn, :follow, %{acct: "https://mastodon.social/users/emelie"})) +        |> html_response(200) + +      assert response =~ "Log in to follow" +    end + +    test "show follow page if the `acct` is a account link", %{conn: conn} do +      user = insert(:user) + +      response = +        conn +        |> assign(:user, user) +        |> get(remote_follow_path(conn, :follow, %{acct: "https://mastodon.social/users/emelie"})) +        |> html_response(200) + +      assert response =~ "Remote follow" +    end + +    test "show follow page with error when user cannot fecth by `acct` link", %{conn: conn} do +      user = insert(:user) + +      assert capture_log(fn -> +               response = +                 conn +                 |> assign(:user, user) +                 |> get( +                   remote_follow_path(conn, :follow, %{ +                     acct: "https://mastodon.social/users/not_found" +                   }) +                 ) +                 |> html_response(200) + +               assert response =~ "Error fetching user" +             end) =~ "Object has been deleted" +    end +  end + +  describe "POST /ostatus_subscribe - do_follow/2 with assigned user " do +    test "required `follow | write:follows` scope", %{conn: conn} do +      user = insert(:user) +      user2 = insert(:user) +      read_token = insert(:oauth_token, user: user, scopes: ["read"]) + +      assert capture_log(fn -> +               response = +                 conn +                 |> assign(:user, user) +                 |> assign(:token, read_token) +                 |> post(remote_follow_path(conn, :do_follow), %{"user" => %{"id" => user2.id}}) +                 |> response(200) + +               assert response =~ "Error following account" +             end) =~ "Insufficient permissions: follow | write:follows." +    end + +    test "follows user", %{conn: conn} do +      user = insert(:user) +      user2 = insert(:user) + +      response = +        conn +        |> assign(:user, user) +        |> assign(:token, insert(:oauth_token, user: user, scopes: ["write:follows"])) +        |> post(remote_follow_path(conn, :do_follow), %{"user" => %{"id" => user2.id}}) +        |> response(200) + +      assert response =~ "Account followed!" +      assert user2.follower_address in User.following(user) +    end + +    test "returns error when user is deactivated", %{conn: conn} do +      user = insert(:user, deactivated: true) +      user2 = insert(:user) + +      response = +        conn +        |> assign(:user, user) +        |> post(remote_follow_path(conn, :do_follow), %{"user" => %{"id" => user2.id}}) +        |> response(200) + +      assert response =~ "Error following account" +    end + +    test "returns error when user is blocked", %{conn: conn} do +      Pleroma.Config.put([:user, :deny_follow_blocked], true) +      user = insert(:user) +      user2 = insert(:user) + +      {:ok, _user_block} = Pleroma.User.block(user2, user) + +      response = +        conn +        |> assign(:user, user) +        |> post(remote_follow_path(conn, :do_follow), %{"user" => %{"id" => user2.id}}) +        |> response(200) + +      assert response =~ "Error following account" +    end + +    test "returns error when followee not found", %{conn: conn} do +      user = insert(:user) + +      response = +        conn +        |> assign(:user, user) +        |> post(remote_follow_path(conn, :do_follow), %{"user" => %{"id" => "jimm"}}) +        |> response(200) + +      assert response =~ "Error following account" +    end + +    test "returns success result when user already in followers", %{conn: conn} do +      user = insert(:user) +      user2 = insert(:user) +      {:ok, _, _, _} = CommonAPI.follow(user, user2) + +      response = +        conn +        |> assign(:user, refresh_record(user)) +        |> assign(:token, insert(:oauth_token, user: user, scopes: ["write:follows"])) +        |> post(remote_follow_path(conn, :do_follow), %{"user" => %{"id" => user2.id}}) +        |> response(200) + +      assert response =~ "Account followed!" +    end +  end + +  describe "POST /ostatus_subscribe - follow/2 without assigned user " do +    test "follows", %{conn: conn} do +      user = insert(:user) +      user2 = insert(:user) + +      response = +        conn +        |> post(remote_follow_path(conn, :do_follow), %{ +          "authorization" => %{"name" => user.nickname, "password" => "test", "id" => user2.id} +        }) +        |> response(200) + +      assert response =~ "Account followed!" +      assert user2.follower_address in User.following(user) +    end + +    test "returns error when followee not found", %{conn: conn} do +      user = insert(:user) + +      response = +        conn +        |> post(remote_follow_path(conn, :do_follow), %{ +          "authorization" => %{"name" => user.nickname, "password" => "test", "id" => "jimm"} +        }) +        |> response(200) + +      assert response =~ "Error following account" +    end + +    test "returns error when login invalid", %{conn: conn} do +      user = insert(:user) + +      response = +        conn +        |> post(remote_follow_path(conn, :do_follow), %{ +          "authorization" => %{"name" => "jimm", "password" => "test", "id" => user.id} +        }) +        |> response(200) + +      assert response =~ "Wrong username or password" +    end + +    test "returns error when password invalid", %{conn: conn} do +      user = insert(:user) +      user2 = insert(:user) + +      response = +        conn +        |> post(remote_follow_path(conn, :do_follow), %{ +          "authorization" => %{"name" => user.nickname, "password" => "42", "id" => user2.id} +        }) +        |> response(200) + +      assert response =~ "Wrong username or password" +    end + +    test "returns error when user is blocked", %{conn: conn} do +      Pleroma.Config.put([:user, :deny_follow_blocked], true) +      user = insert(:user) +      user2 = insert(:user) +      {:ok, _user_block} = Pleroma.User.block(user2, user) + +      response = +        conn +        |> post(remote_follow_path(conn, :do_follow), %{ +          "authorization" => %{"name" => user.nickname, "password" => "test", "id" => user2.id} +        }) +        |> response(200) + +      assert response =~ "Error following account" +    end +  end +end diff --git a/test/web/twitter_api/representers/object_representer_test.exs b/test/web/twitter_api/representers/object_representer_test.exs deleted file mode 100644 index c3cf330f1..000000000 --- a/test/web/twitter_api/representers/object_representer_test.exs +++ /dev/null @@ -1,60 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/> -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Web.TwitterAPI.Representers.ObjectReprenterTest do -  use Pleroma.DataCase - -  alias Pleroma.Object -  alias Pleroma.Web.TwitterAPI.Representers.ObjectRepresenter - -  test "represent an image attachment" do -    object = %Object{ -      id: 5, -      data: %{ -        "type" => "Image", -        "url" => [ -          %{ -            "mediaType" => "sometype", -            "href" => "someurl" -          } -        ], -        "uuid" => 6 -      } -    } - -    expected_object = %{ -      id: 6, -      url: "someurl", -      mimetype: "sometype", -      oembed: false, -      description: nil -    } - -    assert expected_object == ObjectRepresenter.to_map(object) -  end - -  test "represents mastodon-style attachments" do -    object = %Object{ -      id: nil, -      data: %{ -        "mediaType" => "image/png", -        "name" => "blabla", -        "type" => "Document", -        "url" => -          "http://mastodon.example.org/system/media_attachments/files/000/000/001/original/8619f31c6edec470.png" -      } -    } - -    expected_object = %{ -      url: -        "http://mastodon.example.org/system/media_attachments/files/000/000/001/original/8619f31c6edec470.png", -      mimetype: "image/png", -      oembed: false, -      id: nil, -      description: "blabla" -    } - -    assert expected_object == ObjectRepresenter.to_map(object) -  end -end diff --git a/test/web/twitter_api/twitter_api_controller_test.exs b/test/web/twitter_api/twitter_api_controller_test.exs deleted file mode 100644 index 8bb8aa36d..000000000 --- a/test/web/twitter_api/twitter_api_controller_test.exs +++ /dev/null @@ -1,2159 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Web.TwitterAPI.ControllerTest do -  use Pleroma.Web.ConnCase -  alias Comeonin.Pbkdf2 -  alias Ecto.Changeset -  alias Pleroma.Activity -  alias Pleroma.Builders.ActivityBuilder -  alias Pleroma.Builders.UserBuilder -  alias Pleroma.Notification -  alias Pleroma.Object -  alias Pleroma.Repo -  alias Pleroma.User -  alias Pleroma.Web.ActivityPub.ActivityPub -  alias Pleroma.Web.CommonAPI -  alias Pleroma.Web.OAuth.Token -  alias Pleroma.Web.TwitterAPI.ActivityView -  alias Pleroma.Web.TwitterAPI.Controller -  alias Pleroma.Web.TwitterAPI.NotificationView -  alias Pleroma.Web.TwitterAPI.TwitterAPI -  alias Pleroma.Web.TwitterAPI.UserView - -  import Mock -  import Pleroma.Factory -  import Swoosh.TestAssertions - -  @banner "data:image/gif;base64,R0lGODlhEAAQAMQAAORHHOVSKudfOulrSOp3WOyDZu6QdvCchPGolfO0o/XBs/fNwfjZ0frl3/zy7////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAkAABAALAAAAAAQABAAAAVVICSOZGlCQAosJ6mu7fiyZeKqNKToQGDsM8hBADgUXoGAiqhSvp5QAnQKGIgUhwFUYLCVDFCrKUE1lBavAViFIDlTImbKC5Gm2hB0SlBCBMQiB0UjIQA7" - -  describe "POST /api/account/update_profile_banner" do -    test "it updates the banner", %{conn: conn} do -      user = insert(:user) - -      conn -      |> assign(:user, user) -      |> post(authenticated_twitter_api__path(conn, :update_banner), %{"banner" => @banner}) -      |> json_response(200) - -      user = refresh_record(user) -      assert user.info.banner["type"] == "Image" -    end - -    test "profile banner can be reset", %{conn: conn} do -      user = insert(:user) - -      conn -      |> assign(:user, user) -      |> post(authenticated_twitter_api__path(conn, :update_banner), %{"banner" => ""}) -      |> json_response(200) - -      user = refresh_record(user) -      assert user.info.banner == %{} -    end -  end - -  describe "POST /api/qvitter/update_background_image" do -    test "it updates the background", %{conn: conn} do -      user = insert(:user) - -      conn -      |> assign(:user, user) -      |> post(authenticated_twitter_api__path(conn, :update_background), %{"img" => @banner}) -      |> json_response(200) - -      user = refresh_record(user) -      assert user.info.background["type"] == "Image" -    end - -    test "background can be reset", %{conn: conn} do -      user = insert(:user) - -      conn -      |> assign(:user, user) -      |> post(authenticated_twitter_api__path(conn, :update_background), %{"img" => ""}) -      |> json_response(200) - -      user = refresh_record(user) -      assert user.info.background == %{} -    end -  end - -  describe "POST /api/account/verify_credentials" do -    setup [:valid_user] - -    test "without valid credentials", %{conn: conn} do -      conn = post(conn, "/api/account/verify_credentials.json") -      assert json_response(conn, 403) == %{"error" => "Invalid credentials."} -    end - -    test "with credentials", %{conn: conn, user: user} do -      response = -        conn -        |> with_credentials(user.nickname, "test") -        |> post("/api/account/verify_credentials.json") -        |> json_response(200) - -      assert response == -               UserView.render("show.json", %{user: user, token: response["token"], for: user}) -    end -  end - -  describe "POST /statuses/update.json" do -    setup [:valid_user] - -    test "without valid credentials", %{conn: conn} do -      conn = post(conn, "/api/statuses/update.json") -      assert json_response(conn, 403) == %{"error" => "Invalid credentials."} -    end - -    test "with credentials", %{conn: conn, user: user} do -      conn_with_creds = conn |> with_credentials(user.nickname, "test") -      request_path = "/api/statuses/update.json" - -      error_response = %{ -        "request" => request_path, -        "error" => "Client must provide a 'status' parameter with a value." -      } - -      conn = -        conn_with_creds -        |> post(request_path) - -      assert json_response(conn, 400) == error_response - -      conn = -        conn_with_creds -        |> post(request_path, %{status: ""}) - -      assert json_response(conn, 400) == error_response - -      conn = -        conn_with_creds -        |> post(request_path, %{status: " "}) - -      assert json_response(conn, 400) == error_response - -      # we post with visibility private in order to avoid triggering relay -      conn = -        conn_with_creds -        |> post(request_path, %{status: "Nice meme.", visibility: "private"}) - -      assert json_response(conn, 200) == -               ActivityView.render("activity.json", %{ -                 activity: Repo.one(Activity), -                 user: user, -                 for: user -               }) -    end -  end - -  describe "GET /statuses/public_timeline.json" do -    setup [:valid_user] - -    test "returns statuses", %{conn: conn} do -      user = insert(:user) -      activities = ActivityBuilder.insert_list(30, %{}, %{user: user}) -      ActivityBuilder.insert_list(10, %{}, %{user: user}) -      since_id = List.last(activities).id - -      conn = -        conn -        |> get("/api/statuses/public_timeline.json", %{since_id: since_id}) - -      response = json_response(conn, 200) - -      assert length(response) == 10 -    end - -    test "returns 403 to unauthenticated request when the instance is not public", %{conn: conn} do -      Pleroma.Config.put([:instance, :public], false) - -      conn -      |> get("/api/statuses/public_timeline.json") -      |> json_response(403) - -      Pleroma.Config.put([:instance, :public], true) -    end - -    test "returns 200 to authenticated request when the instance is not public", -         %{conn: conn, user: user} do -      Pleroma.Config.put([:instance, :public], false) - -      conn -      |> with_credentials(user.nickname, "test") -      |> get("/api/statuses/public_timeline.json") -      |> json_response(200) - -      Pleroma.Config.put([:instance, :public], true) -    end - -    test "returns 200 to unauthenticated request when the instance is public", %{conn: conn} do -      conn -      |> get("/api/statuses/public_timeline.json") -      |> json_response(200) -    end - -    test "returns 200 to authenticated request when the instance is public", -         %{conn: conn, user: user} do -      conn -      |> with_credentials(user.nickname, "test") -      |> get("/api/statuses/public_timeline.json") -      |> json_response(200) -    end - -    test_with_mock "treats user as unauthenticated if `assigns[:token]` is present but lacks `read` permission", -                   Controller, -                   [:passthrough], -                   [] do -      token = insert(:oauth_token, scopes: ["write"]) - -      build_conn() -      |> put_req_header("authorization", "Bearer #{token.token}") -      |> get("/api/statuses/public_timeline.json") -      |> json_response(200) - -      assert called(Controller.public_timeline(%{assigns: %{user: nil}}, :_)) -    end -  end - -  describe "GET /statuses/public_and_external_timeline.json" do -    setup [:valid_user] - -    test "returns 403 to unauthenticated request when the instance is not public", %{conn: conn} do -      Pleroma.Config.put([:instance, :public], false) - -      conn -      |> get("/api/statuses/public_and_external_timeline.json") -      |> json_response(403) - -      Pleroma.Config.put([:instance, :public], true) -    end - -    test "returns 200 to authenticated request when the instance is not public", -         %{conn: conn, user: user} do -      Pleroma.Config.put([:instance, :public], false) - -      conn -      |> with_credentials(user.nickname, "test") -      |> get("/api/statuses/public_and_external_timeline.json") -      |> json_response(200) - -      Pleroma.Config.put([:instance, :public], true) -    end - -    test "returns 200 to unauthenticated request when the instance is public", %{conn: conn} do -      conn -      |> get("/api/statuses/public_and_external_timeline.json") -      |> json_response(200) -    end - -    test "returns 200 to authenticated request when the instance is public", -         %{conn: conn, user: user} do -      conn -      |> with_credentials(user.nickname, "test") -      |> get("/api/statuses/public_and_external_timeline.json") -      |> json_response(200) -    end -  end - -  describe "GET /statuses/show/:id.json" do -    test "returns one status", %{conn: conn} do -      user = insert(:user) -      {:ok, activity} = CommonAPI.post(user, %{"status" => "Hey!"}) -      actor = User.get_cached_by_ap_id(activity.data["actor"]) - -      conn = -        conn -        |> get("/api/statuses/show/#{activity.id}.json") - -      response = json_response(conn, 200) - -      assert response == ActivityView.render("activity.json", %{activity: activity, user: actor}) -    end -  end - -  describe "GET /users/show.json" do -    test "gets user with screen_name", %{conn: conn} do -      user = insert(:user) - -      conn = -        conn -        |> get("/api/users/show.json", %{"screen_name" => user.nickname}) - -      response = json_response(conn, 200) - -      assert response["id"] == user.id -    end - -    test "gets user with user_id", %{conn: conn} do -      user = insert(:user) - -      conn = -        conn -        |> get("/api/users/show.json", %{"user_id" => user.id}) - -      response = json_response(conn, 200) - -      assert response["id"] == user.id -    end - -    test "gets a user for a logged in user", %{conn: conn} do -      user = insert(:user) -      logged_in = insert(:user) - -      {:ok, logged_in, user, _activity} = TwitterAPI.follow(logged_in, %{"user_id" => user.id}) - -      conn = -        conn -        |> with_credentials(logged_in.nickname, "test") -        |> get("/api/users/show.json", %{"user_id" => user.id}) - -      response = json_response(conn, 200) - -      assert response["following"] == true -    end -  end - -  describe "GET /statusnet/conversation/:id.json" do -    test "returns the statuses in the conversation", %{conn: conn} do -      {:ok, _user} = UserBuilder.insert() -      {:ok, activity} = ActivityBuilder.insert(%{"type" => "Create", "context" => "2hu"}) -      {:ok, _activity_two} = ActivityBuilder.insert(%{"type" => "Create", "context" => "2hu"}) -      {:ok, _activity_three} = ActivityBuilder.insert(%{"type" => "Create", "context" => "3hu"}) - -      conn = -        conn -        |> get("/api/statusnet/conversation/#{activity.data["context_id"]}.json") - -      response = json_response(conn, 200) - -      assert length(response) == 2 -    end -  end - -  describe "GET /statuses/friends_timeline.json" do -    setup [:valid_user] - -    test "without valid credentials", %{conn: conn} do -      conn = get(conn, "/api/statuses/friends_timeline.json") -      assert json_response(conn, 403) == %{"error" => "Invalid credentials."} -    end - -    test "with credentials", %{conn: conn, user: current_user} do -      user = insert(:user) - -      activities = -        ActivityBuilder.insert_list(30, %{"to" => [User.ap_followers(user)]}, %{user: user}) - -      returned_activities = -        ActivityBuilder.insert_list(10, %{"to" => [User.ap_followers(user)]}, %{user: user}) - -      other_user = insert(:user) -      ActivityBuilder.insert_list(10, %{}, %{user: other_user}) -      since_id = List.last(activities).id - -      current_user = -        Changeset.change(current_user, following: [User.ap_followers(user)]) -        |> Repo.update!() - -      conn = -        conn -        |> with_credentials(current_user.nickname, "test") -        |> get("/api/statuses/friends_timeline.json", %{since_id: since_id}) - -      response = json_response(conn, 200) - -      assert length(response) == 10 - -      assert response == -               Enum.map(returned_activities, fn activity -> -                 ActivityView.render("activity.json", %{ -                   activity: activity, -                   user: User.get_cached_by_ap_id(activity.data["actor"]), -                   for: current_user -                 }) -               end) -    end -  end - -  describe "GET /statuses/dm_timeline.json" do -    test "it show direct messages", %{conn: conn} do -      user_one = insert(:user) -      user_two = insert(:user) - -      {:ok, user_two} = User.follow(user_two, user_one) - -      {:ok, direct} = -        CommonAPI.post(user_one, %{ -          "status" => "Hi @#{user_two.nickname}!", -          "visibility" => "direct" -        }) - -      {:ok, direct_two} = -        CommonAPI.post(user_two, %{ -          "status" => "Hi @#{user_one.nickname}!", -          "visibility" => "direct" -        }) - -      {:ok, _follower_only} = -        CommonAPI.post(user_one, %{ -          "status" => "Hi @#{user_two.nickname}!", -          "visibility" => "private" -        }) - -      # Only direct should be visible here -      res_conn = -        conn -        |> assign(:user, user_two) -        |> get("/api/statuses/dm_timeline.json") - -      [status, status_two] = json_response(res_conn, 200) -      assert status["id"] == direct_two.id -      assert status_two["id"] == direct.id -    end - -    test "doesn't include DMs from blocked users", %{conn: conn} do -      blocker = insert(:user) -      blocked = insert(:user) -      user = insert(:user) -      {:ok, blocker} = User.block(blocker, blocked) - -      {:ok, _blocked_direct} = -        CommonAPI.post(blocked, %{ -          "status" => "Hi @#{blocker.nickname}!", -          "visibility" => "direct" -        }) - -      {:ok, direct} = -        CommonAPI.post(user, %{ -          "status" => "Hi @#{blocker.nickname}!", -          "visibility" => "direct" -        }) - -      res_conn = -        conn -        |> assign(:user, blocker) -        |> get("/api/statuses/dm_timeline.json") - -      [status] = json_response(res_conn, 200) -      assert status["id"] == direct.id -    end -  end - -  describe "GET /statuses/mentions.json" do -    setup [:valid_user] - -    test "without valid credentials", %{conn: conn} do -      conn = get(conn, "/api/statuses/mentions.json") -      assert json_response(conn, 403) == %{"error" => "Invalid credentials."} -    end - -    test "with credentials", %{conn: conn, user: current_user} do -      {:ok, activity} = -        CommonAPI.post(current_user, %{ -          "status" => "why is tenshi eating a corndog so cute?", -          "visibility" => "public" -        }) - -      conn = -        conn -        |> with_credentials(current_user.nickname, "test") -        |> get("/api/statuses/mentions.json") - -      response = json_response(conn, 200) - -      assert length(response) == 1 - -      assert Enum.at(response, 0) == -               ActivityView.render("activity.json", %{ -                 user: current_user, -                 for: current_user, -                 activity: activity -               }) -    end - -    test "does not show DMs in mentions timeline", %{conn: conn, user: current_user} do -      {:ok, _activity} = -        CommonAPI.post(current_user, %{ -          "status" => "Have you guys ever seen how cute tenshi eating a corndog is?", -          "visibility" => "direct" -        }) - -      conn = -        conn -        |> with_credentials(current_user.nickname, "test") -        |> get("/api/statuses/mentions.json") - -      response = json_response(conn, 200) - -      assert Enum.empty?(response) -    end -  end - -  describe "GET /api/qvitter/statuses/notifications.json" do -    setup [:valid_user] - -    test "without valid credentials", %{conn: conn} do -      conn = get(conn, "/api/qvitter/statuses/notifications.json") -      assert json_response(conn, 403) == %{"error" => "Invalid credentials."} -    end - -    test "with credentials", %{conn: conn, user: current_user} do -      other_user = insert(:user) - -      {:ok, _activity} = -        ActivityBuilder.insert(%{"to" => [current_user.ap_id]}, %{user: other_user}) - -      conn = -        conn -        |> with_credentials(current_user.nickname, "test") -        |> get("/api/qvitter/statuses/notifications.json") - -      response = json_response(conn, 200) - -      assert length(response) == 1 - -      assert response == -               NotificationView.render("notification.json", %{ -                 notifications: Notification.for_user(current_user), -                 for: current_user -               }) -    end - -    test "muted user", %{conn: conn, user: current_user} do -      other_user = insert(:user) - -      {:ok, current_user} = User.mute(current_user, other_user) - -      {:ok, _activity} = -        ActivityBuilder.insert(%{"to" => [current_user.ap_id]}, %{user: other_user}) - -      conn = -        conn -        |> with_credentials(current_user.nickname, "test") -        |> get("/api/qvitter/statuses/notifications.json") - -      assert json_response(conn, 200) == [] -    end - -    test "muted user with with_muted parameter", %{conn: conn, user: current_user} do -      other_user = insert(:user) - -      {:ok, current_user} = User.mute(current_user, other_user) - -      {:ok, _activity} = -        ActivityBuilder.insert(%{"to" => [current_user.ap_id]}, %{user: other_user}) - -      conn = -        conn -        |> with_credentials(current_user.nickname, "test") -        |> get("/api/qvitter/statuses/notifications.json", %{"with_muted" => "true"}) - -      assert length(json_response(conn, 200)) == 1 -    end -  end - -  describe "POST /api/qvitter/statuses/notifications/read" do -    setup [:valid_user] - -    test "without valid credentials", %{conn: conn} do -      conn = post(conn, "/api/qvitter/statuses/notifications/read", %{"latest_id" => 1_234_567}) -      assert json_response(conn, 403) == %{"error" => "Invalid credentials."} -    end - -    test "with credentials, without any params", %{conn: conn, user: current_user} do -      conn = -        conn -        |> with_credentials(current_user.nickname, "test") -        |> post("/api/qvitter/statuses/notifications/read") - -      assert json_response(conn, 400) == %{ -               "error" => "You need to specify latest_id", -               "request" => "/api/qvitter/statuses/notifications/read" -             } -    end - -    test "with credentials, with params", %{conn: conn, user: current_user} do -      other_user = insert(:user) - -      {:ok, _activity} = -        ActivityBuilder.insert(%{"to" => [current_user.ap_id]}, %{user: other_user}) - -      response_conn = -        conn -        |> with_credentials(current_user.nickname, "test") -        |> get("/api/qvitter/statuses/notifications.json") - -      [notification] = response = json_response(response_conn, 200) - -      assert length(response) == 1 - -      assert notification["is_seen"] == 0 - -      response_conn = -        conn -        |> with_credentials(current_user.nickname, "test") -        |> post("/api/qvitter/statuses/notifications/read", %{"latest_id" => notification["id"]}) - -      [notification] = response = json_response(response_conn, 200) - -      assert length(response) == 1 - -      assert notification["is_seen"] == 1 -    end -  end - -  describe "GET /statuses/user_timeline.json" do -    setup [:valid_user] - -    test "without any params", %{conn: conn} do -      conn = get(conn, "/api/statuses/user_timeline.json") - -      assert json_response(conn, 400) == %{ -               "error" => "You need to specify screen_name or user_id", -               "request" => "/api/statuses/user_timeline.json" -             } -    end - -    test "with user_id", %{conn: conn} do -      user = insert(:user) -      {:ok, activity} = ActivityBuilder.insert(%{"id" => 1}, %{user: user}) - -      conn = get(conn, "/api/statuses/user_timeline.json", %{"user_id" => user.id}) -      response = json_response(conn, 200) -      assert length(response) == 1 - -      assert Enum.at(response, 0) == -               ActivityView.render("activity.json", %{user: user, activity: activity}) -    end - -    test "with screen_name", %{conn: conn} do -      user = insert(:user) -      {:ok, activity} = ActivityBuilder.insert(%{"id" => 1}, %{user: user}) - -      conn = get(conn, "/api/statuses/user_timeline.json", %{"screen_name" => user.nickname}) -      response = json_response(conn, 200) -      assert length(response) == 1 - -      assert Enum.at(response, 0) == -               ActivityView.render("activity.json", %{user: user, activity: activity}) -    end - -    test "with credentials", %{conn: conn, user: current_user} do -      {:ok, activity} = ActivityBuilder.insert(%{"id" => 1}, %{user: current_user}) - -      conn = -        conn -        |> with_credentials(current_user.nickname, "test") -        |> get("/api/statuses/user_timeline.json") - -      response = json_response(conn, 200) - -      assert length(response) == 1 - -      assert Enum.at(response, 0) == -               ActivityView.render("activity.json", %{ -                 user: current_user, -                 for: current_user, -                 activity: activity -               }) -    end - -    test "with credentials with user_id", %{conn: conn, user: current_user} do -      user = insert(:user) -      {:ok, activity} = ActivityBuilder.insert(%{"id" => 1}, %{user: user}) - -      conn = -        conn -        |> with_credentials(current_user.nickname, "test") -        |> get("/api/statuses/user_timeline.json", %{"user_id" => user.id}) - -      response = json_response(conn, 200) - -      assert length(response) == 1 - -      assert Enum.at(response, 0) == -               ActivityView.render("activity.json", %{user: user, activity: activity}) -    end - -    test "with credentials screen_name", %{conn: conn, user: current_user} do -      user = insert(:user) -      {:ok, activity} = ActivityBuilder.insert(%{"id" => 1}, %{user: user}) - -      conn = -        conn -        |> with_credentials(current_user.nickname, "test") -        |> get("/api/statuses/user_timeline.json", %{"screen_name" => user.nickname}) - -      response = json_response(conn, 200) - -      assert length(response) == 1 - -      assert Enum.at(response, 0) == -               ActivityView.render("activity.json", %{user: user, activity: activity}) -    end - -    test "with credentials with user_id, excluding RTs", %{conn: conn, user: current_user} do -      user = insert(:user) -      {:ok, activity} = ActivityBuilder.insert(%{"id" => 1, "type" => "Create"}, %{user: user}) -      {:ok, _} = ActivityBuilder.insert(%{"id" => 2, "type" => "Announce"}, %{user: user}) - -      conn = -        conn -        |> with_credentials(current_user.nickname, "test") -        |> get("/api/statuses/user_timeline.json", %{ -          "user_id" => user.id, -          "include_rts" => "false" -        }) - -      response = json_response(conn, 200) - -      assert length(response) == 1 - -      assert Enum.at(response, 0) == -               ActivityView.render("activity.json", %{user: user, activity: activity}) - -      conn = -        conn -        |> get("/api/statuses/user_timeline.json", %{"user_id" => user.id, "include_rts" => "0"}) - -      response = json_response(conn, 200) - -      assert length(response) == 1 - -      assert Enum.at(response, 0) == -               ActivityView.render("activity.json", %{user: user, activity: activity}) -    end -  end - -  describe "POST /friendships/create.json" do -    setup [:valid_user] - -    test "without valid credentials", %{conn: conn} do -      conn = post(conn, "/api/friendships/create.json") -      assert json_response(conn, 403) == %{"error" => "Invalid credentials."} -    end - -    test "with credentials", %{conn: conn, user: current_user} do -      followed = insert(:user) - -      conn = -        conn -        |> with_credentials(current_user.nickname, "test") -        |> post("/api/friendships/create.json", %{user_id: followed.id}) - -      current_user = User.get_cached_by_id(current_user.id) -      assert User.ap_followers(followed) in current_user.following - -      assert json_response(conn, 200) == -               UserView.render("show.json", %{user: followed, for: current_user}) -    end - -    test "for restricted account", %{conn: conn, user: current_user} do -      followed = insert(:user, info: %User.Info{locked: true}) - -      conn = -        conn -        |> with_credentials(current_user.nickname, "test") -        |> post("/api/friendships/create.json", %{user_id: followed.id}) - -      current_user = User.get_cached_by_id(current_user.id) -      followed = User.get_cached_by_id(followed.id) - -      refute User.ap_followers(followed) in current_user.following - -      assert json_response(conn, 200) == -               UserView.render("show.json", %{user: followed, for: current_user}) -    end -  end - -  describe "POST /friendships/destroy.json" do -    setup [:valid_user] - -    test "without valid credentials", %{conn: conn} do -      conn = post(conn, "/api/friendships/destroy.json") -      assert json_response(conn, 403) == %{"error" => "Invalid credentials."} -    end - -    test "with credentials", %{conn: conn, user: current_user} do -      followed = insert(:user) - -      {:ok, current_user} = User.follow(current_user, followed) -      assert User.ap_followers(followed) in current_user.following -      ActivityPub.follow(current_user, followed) - -      conn = -        conn -        |> with_credentials(current_user.nickname, "test") -        |> post("/api/friendships/destroy.json", %{user_id: followed.id}) - -      current_user = User.get_cached_by_id(current_user.id) -      assert current_user.following == [current_user.ap_id] - -      assert json_response(conn, 200) == -               UserView.render("show.json", %{user: followed, for: current_user}) -    end -  end - -  describe "POST /blocks/create.json" do -    setup [:valid_user] - -    test "without valid credentials", %{conn: conn} do -      conn = post(conn, "/api/blocks/create.json") -      assert json_response(conn, 403) == %{"error" => "Invalid credentials."} -    end - -    test "with credentials", %{conn: conn, user: current_user} do -      blocked = insert(:user) - -      conn = -        conn -        |> with_credentials(current_user.nickname, "test") -        |> post("/api/blocks/create.json", %{user_id: blocked.id}) - -      current_user = User.get_cached_by_id(current_user.id) -      assert User.blocks?(current_user, blocked) - -      assert json_response(conn, 200) == -               UserView.render("show.json", %{user: blocked, for: current_user}) -    end -  end - -  describe "POST /blocks/destroy.json" do -    setup [:valid_user] - -    test "without valid credentials", %{conn: conn} do -      conn = post(conn, "/api/blocks/destroy.json") -      assert json_response(conn, 403) == %{"error" => "Invalid credentials."} -    end - -    test "with credentials", %{conn: conn, user: current_user} do -      blocked = insert(:user) - -      {:ok, current_user, blocked} = TwitterAPI.block(current_user, %{"user_id" => blocked.id}) -      assert User.blocks?(current_user, blocked) - -      conn = -        conn -        |> with_credentials(current_user.nickname, "test") -        |> post("/api/blocks/destroy.json", %{user_id: blocked.id}) - -      current_user = User.get_cached_by_id(current_user.id) -      assert current_user.info.blocks == [] - -      assert json_response(conn, 200) == -               UserView.render("show.json", %{user: blocked, for: current_user}) -    end -  end - -  describe "GET /help/test.json" do -    test "returns \"ok\"", %{conn: conn} do -      conn = get(conn, "/api/help/test.json") -      assert json_response(conn, 200) == "ok" -    end -  end - -  describe "POST /api/qvitter/update_avatar.json" do -    setup [:valid_user] - -    test "without valid credentials", %{conn: conn} do -      conn = post(conn, "/api/qvitter/update_avatar.json") -      assert json_response(conn, 403) == %{"error" => "Invalid credentials."} -    end - -    test "with credentials", %{conn: conn, user: current_user} do -      avatar_image = File.read!("test/fixtures/avatar_data_uri") - -      conn = -        conn -        |> with_credentials(current_user.nickname, "test") -        |> post("/api/qvitter/update_avatar.json", %{img: avatar_image}) - -      current_user = User.get_cached_by_id(current_user.id) -      assert is_map(current_user.avatar) - -      assert json_response(conn, 200) == -               UserView.render("show.json", %{user: current_user, for: current_user}) -    end - -    test "user avatar can be reset", %{conn: conn, user: current_user} do -      conn = -        conn -        |> with_credentials(current_user.nickname, "test") -        |> post("/api/qvitter/update_avatar.json", %{img: ""}) - -      current_user = User.get_cached_by_id(current_user.id) -      assert current_user.avatar == nil - -      assert json_response(conn, 200) == -               UserView.render("show.json", %{user: current_user, for: current_user}) -    end -  end - -  describe "GET /api/qvitter/mutes.json" do -    setup [:valid_user] - -    test "unimplemented mutes without valid credentials", %{conn: conn} do -      conn = get(conn, "/api/qvitter/mutes.json") -      assert json_response(conn, 403) == %{"error" => "Invalid credentials."} -    end - -    test "unimplemented mutes with credentials", %{conn: conn, user: current_user} do -      response = -        conn -        |> with_credentials(current_user.nickname, "test") -        |> get("/api/qvitter/mutes.json") -        |> json_response(200) - -      assert [] = response -    end -  end - -  describe "POST /api/favorites/create/:id" do -    setup [:valid_user] - -    test "without valid credentials", %{conn: conn} do -      note_activity = insert(:note_activity) -      conn = post(conn, "/api/favorites/create/#{note_activity.id}.json") -      assert json_response(conn, 403) == %{"error" => "Invalid credentials."} -    end - -    test "with credentials", %{conn: conn, user: current_user} do -      note_activity = insert(:note_activity) - -      conn = -        conn -        |> with_credentials(current_user.nickname, "test") -        |> post("/api/favorites/create/#{note_activity.id}.json") - -      assert json_response(conn, 200) -    end - -    test "with credentials, invalid param", %{conn: conn, user: current_user} do -      conn = -        conn -        |> with_credentials(current_user.nickname, "test") -        |> post("/api/favorites/create/wrong.json") - -      assert json_response(conn, 400) -    end - -    test "with credentials, invalid activity", %{conn: conn, user: current_user} do -      conn = -        conn -        |> with_credentials(current_user.nickname, "test") -        |> post("/api/favorites/create/1.json") - -      assert json_response(conn, 400) -    end -  end - -  describe "POST /api/favorites/destroy/:id" do -    setup [:valid_user] - -    test "without valid credentials", %{conn: conn} do -      note_activity = insert(:note_activity) -      conn = post(conn, "/api/favorites/destroy/#{note_activity.id}.json") -      assert json_response(conn, 403) == %{"error" => "Invalid credentials."} -    end - -    test "with credentials", %{conn: conn, user: current_user} do -      note_activity = insert(:note_activity) -      object = Object.normalize(note_activity) -      ActivityPub.like(current_user, object) - -      conn = -        conn -        |> with_credentials(current_user.nickname, "test") -        |> post("/api/favorites/destroy/#{note_activity.id}.json") - -      assert json_response(conn, 200) -    end -  end - -  describe "POST /api/statuses/retweet/:id" do -    setup [:valid_user] - -    test "without valid credentials", %{conn: conn} do -      note_activity = insert(:note_activity) -      conn = post(conn, "/api/statuses/retweet/#{note_activity.id}.json") -      assert json_response(conn, 403) == %{"error" => "Invalid credentials."} -    end - -    test "with credentials", %{conn: conn, user: current_user} do -      note_activity = insert(:note_activity) - -      request_path = "/api/statuses/retweet/#{note_activity.id}.json" - -      response = -        conn -        |> with_credentials(current_user.nickname, "test") -        |> post(request_path) - -      activity = Activity.get_by_id(note_activity.id) -      activity_user = User.get_cached_by_ap_id(note_activity.data["actor"]) - -      assert json_response(response, 200) == -               ActivityView.render("activity.json", %{ -                 user: activity_user, -                 for: current_user, -                 activity: activity -               }) -    end -  end - -  describe "POST /api/statuses/unretweet/:id" do -    setup [:valid_user] - -    test "without valid credentials", %{conn: conn} do -      note_activity = insert(:note_activity) -      conn = post(conn, "/api/statuses/unretweet/#{note_activity.id}.json") -      assert json_response(conn, 403) == %{"error" => "Invalid credentials."} -    end - -    test "with credentials", %{conn: conn, user: current_user} do -      note_activity = insert(:note_activity) - -      request_path = "/api/statuses/retweet/#{note_activity.id}.json" - -      _response = -        conn -        |> with_credentials(current_user.nickname, "test") -        |> post(request_path) - -      request_path = String.replace(request_path, "retweet", "unretweet") - -      response = -        conn -        |> with_credentials(current_user.nickname, "test") -        |> post(request_path) - -      activity = Activity.get_by_id(note_activity.id) -      activity_user = User.get_cached_by_ap_id(note_activity.data["actor"]) - -      assert json_response(response, 200) == -               ActivityView.render("activity.json", %{ -                 user: activity_user, -                 for: current_user, -                 activity: activity -               }) -    end -  end - -  describe "POST /api/account/register" do -    test "it creates a new user", %{conn: conn} do -      data = %{ -        "nickname" => "lain", -        "email" => "lain@wired.jp", -        "fullname" => "lain iwakura", -        "bio" => "close the world.", -        "password" => "bear", -        "confirm" => "bear" -      } - -      conn = -        conn -        |> post("/api/account/register", data) - -      user = json_response(conn, 200) - -      fetched_user = User.get_cached_by_nickname("lain") -      assert user == UserView.render("show.json", %{user: fetched_user}) -    end - -    test "it returns errors on a problem", %{conn: conn} do -      data = %{ -        "email" => "lain@wired.jp", -        "fullname" => "lain iwakura", -        "bio" => "close the world.", -        "password" => "bear", -        "confirm" => "bear" -      } - -      conn = -        conn -        |> post("/api/account/register", data) - -      errors = json_response(conn, 400) - -      assert is_binary(errors["error"]) -    end -  end - -  describe "POST /api/account/password_reset, with valid parameters" do -    setup %{conn: conn} do -      user = insert(:user) -      conn = post(conn, "/api/account/password_reset?email=#{user.email}") -      %{conn: conn, user: user} -    end - -    test "it returns 204", %{conn: conn} do -      assert json_response(conn, :no_content) -    end - -    test "it creates a PasswordResetToken record for user", %{user: user} do -      token_record = Repo.get_by(Pleroma.PasswordResetToken, user_id: user.id) -      assert token_record -    end - -    test "it sends an email to user", %{user: user} do -      token_record = Repo.get_by(Pleroma.PasswordResetToken, user_id: user.id) - -      email = Pleroma.Emails.UserEmail.password_reset_email(user, token_record.token) -      notify_email = Pleroma.Config.get([:instance, :notify_email]) -      instance_name = Pleroma.Config.get([:instance, :name]) - -      assert_email_sent( -        from: {instance_name, notify_email}, -        to: {user.name, user.email}, -        html_body: email.html_body -      ) -    end -  end - -  describe "POST /api/account/password_reset, with invalid parameters" do -    setup [:valid_user] - -    test "it returns 404 when user is not found", %{conn: conn, user: user} do -      conn = post(conn, "/api/account/password_reset?email=nonexisting_#{user.email}") -      assert conn.status == 404 -      assert conn.resp_body == "" -    end - -    test "it returns 400 when user is not local", %{conn: conn, user: user} do -      {:ok, user} = Repo.update(Changeset.change(user, local: false)) -      conn = post(conn, "/api/account/password_reset?email=#{user.email}") -      assert conn.status == 400 -      assert conn.resp_body == "" -    end -  end - -  describe "GET /api/account/confirm_email/:id/:token" do -    setup do -      user = insert(:user) -      info_change = User.Info.confirmation_changeset(user.info, need_confirmation: true) - -      {:ok, user} = -        user -        |> Changeset.change() -        |> Changeset.put_embed(:info, info_change) -        |> Repo.update() - -      assert user.info.confirmation_pending - -      [user: user] -    end - -    test "it redirects to root url", %{conn: conn, user: user} do -      conn = get(conn, "/api/account/confirm_email/#{user.id}/#{user.info.confirmation_token}") - -      assert 302 == conn.status -    end - -    test "it confirms the user account", %{conn: conn, user: user} do -      get(conn, "/api/account/confirm_email/#{user.id}/#{user.info.confirmation_token}") - -      user = User.get_cached_by_id(user.id) - -      refute user.info.confirmation_pending -      refute user.info.confirmation_token -    end - -    test "it returns 500 if user cannot be found by id", %{conn: conn, user: user} do -      conn = get(conn, "/api/account/confirm_email/0/#{user.info.confirmation_token}") - -      assert 500 == conn.status -    end - -    test "it returns 500 if token is invalid", %{conn: conn, user: user} do -      conn = get(conn, "/api/account/confirm_email/#{user.id}/wrong_token") - -      assert 500 == conn.status -    end -  end - -  describe "POST /api/account/resend_confirmation_email" do -    setup do -      setting = Pleroma.Config.get([:instance, :account_activation_required]) - -      unless setting do -        Pleroma.Config.put([:instance, :account_activation_required], true) -        on_exit(fn -> Pleroma.Config.put([:instance, :account_activation_required], setting) end) -      end - -      user = insert(:user) -      info_change = User.Info.confirmation_changeset(user.info, need_confirmation: true) - -      {:ok, user} = -        user -        |> Changeset.change() -        |> Changeset.put_embed(:info, info_change) -        |> Repo.update() - -      assert user.info.confirmation_pending - -      [user: user] -    end - -    test "it returns 204 No Content", %{conn: conn, user: user} do -      conn -      |> assign(:user, user) -      |> post("/api/account/resend_confirmation_email?email=#{user.email}") -      |> json_response(:no_content) -    end - -    test "it sends confirmation email", %{conn: conn, user: user} do -      conn -      |> assign(:user, user) -      |> post("/api/account/resend_confirmation_email?email=#{user.email}") - -      email = Pleroma.Emails.UserEmail.account_confirmation_email(user) -      notify_email = Pleroma.Config.get([:instance, :notify_email]) -      instance_name = Pleroma.Config.get([:instance, :name]) - -      assert_email_sent( -        from: {instance_name, notify_email}, -        to: {user.name, user.email}, -        html_body: email.html_body -      ) -    end -  end - -  describe "GET /api/externalprofile/show" do -    test "it returns the user", %{conn: conn} do -      user = insert(:user) -      other_user = insert(:user) - -      conn = -        conn -        |> assign(:user, user) -        |> get("/api/externalprofile/show", %{profileurl: other_user.ap_id}) - -      assert json_response(conn, 200) == UserView.render("show.json", %{user: other_user}) -    end -  end - -  describe "GET /api/statuses/followers" do -    test "it returns a user's followers", %{conn: conn} do -      user = insert(:user) -      follower_one = insert(:user) -      follower_two = insert(:user) -      _not_follower = insert(:user) - -      {:ok, follower_one} = User.follow(follower_one, user) -      {:ok, follower_two} = User.follow(follower_two, user) - -      conn = -        conn -        |> assign(:user, user) -        |> get("/api/statuses/followers") - -      expected = UserView.render("index.json", %{users: [follower_one, follower_two], for: user}) -      result = json_response(conn, 200) -      assert Enum.sort(expected) == Enum.sort(result) -    end - -    test "it returns 20 followers per page", %{conn: conn} do -      user = insert(:user) -      followers = insert_list(21, :user) - -      Enum.each(followers, fn follower -> -        User.follow(follower, user) -      end) - -      res_conn = -        conn -        |> assign(:user, user) -        |> get("/api/statuses/followers") - -      result = json_response(res_conn, 200) -      assert length(result) == 20 - -      res_conn = -        conn -        |> assign(:user, user) -        |> get("/api/statuses/followers?page=2") - -      result = json_response(res_conn, 200) -      assert length(result) == 1 -    end - -    test "it returns a given user's followers with user_id", %{conn: conn} do -      user = insert(:user) -      follower_one = insert(:user) -      follower_two = insert(:user) -      not_follower = insert(:user) - -      {:ok, follower_one} = User.follow(follower_one, user) -      {:ok, follower_two} = User.follow(follower_two, user) - -      conn = -        conn -        |> assign(:user, not_follower) -        |> get("/api/statuses/followers", %{"user_id" => user.id}) - -      assert MapSet.equal?( -               MapSet.new(json_response(conn, 200)), -               MapSet.new( -                 UserView.render("index.json", %{ -                   users: [follower_one, follower_two], -                   for: not_follower -                 }) -               ) -             ) -    end - -    test "it returns empty when hide_followers is set to true", %{conn: conn} do -      user = insert(:user, %{info: %{hide_followers: true}}) -      follower_one = insert(:user) -      follower_two = insert(:user) -      not_follower = insert(:user) - -      {:ok, _follower_one} = User.follow(follower_one, user) -      {:ok, _follower_two} = User.follow(follower_two, user) - -      response = -        conn -        |> assign(:user, not_follower) -        |> get("/api/statuses/followers", %{"user_id" => user.id}) -        |> json_response(200) - -      assert [] == response -    end - -    test "it returns the followers when hide_followers is set to true if requested by the user themselves", -         %{ -           conn: conn -         } do -      user = insert(:user, %{info: %{hide_followers: true}}) -      follower_one = insert(:user) -      follower_two = insert(:user) -      _not_follower = insert(:user) - -      {:ok, _follower_one} = User.follow(follower_one, user) -      {:ok, _follower_two} = User.follow(follower_two, user) - -      conn = -        conn -        |> assign(:user, user) -        |> get("/api/statuses/followers", %{"user_id" => user.id}) - -      refute [] == json_response(conn, 200) -    end -  end - -  describe "GET /api/statuses/blocks" do -    test "it returns the list of users blocked by requester", %{conn: conn} do -      user = insert(:user) -      other_user = insert(:user) - -      {:ok, user} = User.block(user, other_user) - -      conn = -        conn -        |> assign(:user, user) -        |> get("/api/statuses/blocks") - -      expected = UserView.render("index.json", %{users: [other_user], for: user}) -      result = json_response(conn, 200) -      assert Enum.sort(expected) == Enum.sort(result) -    end -  end - -  describe "GET /api/statuses/friends" do -    test "it returns the logged in user's friends", %{conn: conn} do -      user = insert(:user) -      followed_one = insert(:user) -      followed_two = insert(:user) -      _not_followed = insert(:user) - -      {:ok, user} = User.follow(user, followed_one) -      {:ok, user} = User.follow(user, followed_two) - -      conn = -        conn -        |> assign(:user, user) -        |> get("/api/statuses/friends") - -      expected = UserView.render("index.json", %{users: [followed_one, followed_two], for: user}) -      result = json_response(conn, 200) -      assert Enum.sort(expected) == Enum.sort(result) -    end - -    test "it returns 20 friends per page, except if 'export' is set to true", %{conn: conn} do -      user = insert(:user) -      followeds = insert_list(21, :user) - -      {:ok, user} = -        Enum.reduce(followeds, {:ok, user}, fn followed, {:ok, user} -> -          User.follow(user, followed) -        end) - -      res_conn = -        conn -        |> assign(:user, user) -        |> get("/api/statuses/friends") - -      result = json_response(res_conn, 200) -      assert length(result) == 20 - -      res_conn = -        conn -        |> assign(:user, user) -        |> get("/api/statuses/friends", %{page: 2}) - -      result = json_response(res_conn, 200) -      assert length(result) == 1 - -      res_conn = -        conn -        |> assign(:user, user) -        |> get("/api/statuses/friends", %{all: true}) - -      result = json_response(res_conn, 200) -      assert length(result) == 21 -    end - -    test "it returns a given user's friends with user_id", %{conn: conn} do -      user = insert(:user) -      followed_one = insert(:user) -      followed_two = insert(:user) -      _not_followed = insert(:user) - -      {:ok, user} = User.follow(user, followed_one) -      {:ok, user} = User.follow(user, followed_two) - -      conn = -        conn -        |> assign(:user, user) -        |> get("/api/statuses/friends", %{"user_id" => user.id}) - -      assert MapSet.equal?( -               MapSet.new(json_response(conn, 200)), -               MapSet.new( -                 UserView.render("index.json", %{users: [followed_one, followed_two], for: user}) -               ) -             ) -    end - -    test "it returns empty when hide_follows is set to true", %{conn: conn} do -      user = insert(:user, %{info: %{hide_follows: true}}) -      followed_one = insert(:user) -      followed_two = insert(:user) -      not_followed = insert(:user) - -      {:ok, user} = User.follow(user, followed_one) -      {:ok, user} = User.follow(user, followed_two) - -      conn = -        conn -        |> assign(:user, not_followed) -        |> get("/api/statuses/friends", %{"user_id" => user.id}) - -      assert [] == json_response(conn, 200) -    end - -    test "it returns friends when hide_follows is set to true if the user themselves request it", -         %{ -           conn: conn -         } do -      user = insert(:user, %{info: %{hide_follows: true}}) -      followed_one = insert(:user) -      followed_two = insert(:user) -      _not_followed = insert(:user) - -      {:ok, _user} = User.follow(user, followed_one) -      {:ok, _user} = User.follow(user, followed_two) - -      response = -        conn -        |> assign(:user, user) -        |> get("/api/statuses/friends", %{"user_id" => user.id}) -        |> json_response(200) - -      refute [] == response -    end - -    test "it returns a given user's friends with screen_name", %{conn: conn} do -      user = insert(:user) -      followed_one = insert(:user) -      followed_two = insert(:user) -      _not_followed = insert(:user) - -      {:ok, user} = User.follow(user, followed_one) -      {:ok, user} = User.follow(user, followed_two) - -      conn = -        conn -        |> assign(:user, user) -        |> get("/api/statuses/friends", %{"screen_name" => user.nickname}) - -      assert MapSet.equal?( -               MapSet.new(json_response(conn, 200)), -               MapSet.new( -                 UserView.render("index.json", %{users: [followed_one, followed_two], for: user}) -               ) -             ) -    end -  end - -  describe "GET /friends/ids" do -    test "it returns a user's friends", %{conn: conn} do -      user = insert(:user) -      followed_one = insert(:user) -      followed_two = insert(:user) -      _not_followed = insert(:user) - -      {:ok, user} = User.follow(user, followed_one) -      {:ok, user} = User.follow(user, followed_two) - -      conn = -        conn -        |> assign(:user, user) -        |> get("/api/friends/ids") - -      expected = [followed_one.id, followed_two.id] - -      assert MapSet.equal?( -               MapSet.new(Poison.decode!(json_response(conn, 200))), -               MapSet.new(expected) -             ) -    end -  end - -  describe "POST /api/account/update_profile.json" do -    test "it updates a user's profile", %{conn: conn} do -      user = insert(:user) -      user2 = insert(:user) - -      conn = -        conn -        |> assign(:user, user) -        |> post("/api/account/update_profile.json", %{ -          "name" => "new name", -          "description" => "hi @#{user2.nickname}" -        }) - -      user = Repo.get!(User, user.id) -      assert user.name == "new name" - -      assert user.bio == -               "hi <span class='h-card'><a data-user='#{user2.id}' class='u-url mention' href='#{ -                 user2.ap_id -               }'>@<span>#{user2.nickname}</span></a></span>" - -      assert json_response(conn, 200) == UserView.render("user.json", %{user: user, for: user}) -    end - -    test "it sets and un-sets hide_follows", %{conn: conn} do -      user = insert(:user) - -      conn -      |> assign(:user, user) -      |> post("/api/account/update_profile.json", %{ -        "hide_follows" => "true" -      }) - -      user = Repo.get!(User, user.id) -      assert user.info.hide_follows == true - -      conn = -        conn -        |> assign(:user, user) -        |> post("/api/account/update_profile.json", %{ -          "hide_follows" => "false" -        }) - -      user = refresh_record(user) -      assert user.info.hide_follows == false -      assert json_response(conn, 200) == UserView.render("user.json", %{user: user, for: user}) -    end - -    test "it sets and un-sets hide_followers", %{conn: conn} do -      user = insert(:user) - -      conn -      |> assign(:user, user) -      |> post("/api/account/update_profile.json", %{ -        "hide_followers" => "true" -      }) - -      user = Repo.get!(User, user.id) -      assert user.info.hide_followers == true - -      conn = -        conn -        |> assign(:user, user) -        |> post("/api/account/update_profile.json", %{ -          "hide_followers" => "false" -        }) - -      user = Repo.get!(User, user.id) -      assert user.info.hide_followers == false -      assert json_response(conn, 200) == UserView.render("user.json", %{user: user, for: user}) -    end - -    test "it sets and un-sets show_role", %{conn: conn} do -      user = insert(:user) - -      conn -      |> assign(:user, user) -      |> post("/api/account/update_profile.json", %{ -        "show_role" => "true" -      }) - -      user = Repo.get!(User, user.id) -      assert user.info.show_role == true - -      conn = -        conn -        |> assign(:user, user) -        |> post("/api/account/update_profile.json", %{ -          "show_role" => "false" -        }) - -      user = Repo.get!(User, user.id) -      assert user.info.show_role == false -      assert json_response(conn, 200) == UserView.render("user.json", %{user: user, for: user}) -    end - -    test "it sets and un-sets skip_thread_containment", %{conn: conn} do -      user = insert(:user) - -      response = -        conn -        |> assign(:user, user) -        |> post("/api/account/update_profile.json", %{"skip_thread_containment" => "true"}) -        |> json_response(200) - -      assert response["pleroma"]["skip_thread_containment"] == true -      user = refresh_record(user) -      assert user.info.skip_thread_containment - -      response = -        conn -        |> assign(:user, user) -        |> post("/api/account/update_profile.json", %{"skip_thread_containment" => "false"}) -        |> json_response(200) - -      assert response["pleroma"]["skip_thread_containment"] == false -      refute refresh_record(user).info.skip_thread_containment -    end - -    test "it locks an account", %{conn: conn} do -      user = insert(:user) - -      conn = -        conn -        |> assign(:user, user) -        |> post("/api/account/update_profile.json", %{ -          "locked" => "true" -        }) - -      user = Repo.get!(User, user.id) -      assert user.info.locked == true - -      assert json_response(conn, 200) == UserView.render("user.json", %{user: user, for: user}) -    end - -    test "it unlocks an account", %{conn: conn} do -      user = insert(:user) - -      conn = -        conn -        |> assign(:user, user) -        |> post("/api/account/update_profile.json", %{ -          "locked" => "false" -        }) - -      user = Repo.get!(User, user.id) -      assert user.info.locked == false - -      assert json_response(conn, 200) == UserView.render("user.json", %{user: user, for: user}) -    end - -    # Broken before the change to class="emoji" and non-<img/> in the DB -    @tag :skip -    test "it formats emojos", %{conn: conn} do -      user = insert(:user) - -      conn = -        conn -        |> assign(:user, user) -        |> post("/api/account/update_profile.json", %{ -          "bio" => "I love our :moominmamma:" -        }) - -      assert response = json_response(conn, 200) - -      assert %{ -               "description" => "I love our :moominmamma:", -               "description_html" => -                 ~s{I love our <img class="emoji" alt="moominmamma" title="moominmamma" src="} <> -                   _ -             } = response - -      conn = -        conn -        |> get("/api/users/show.json?user_id=#{user.nickname}") - -      assert response == json_response(conn, 200) -    end -  end - -  defp valid_user(_context) do -    user = insert(:user) -    [user: user] -  end - -  defp with_credentials(conn, username, password) do -    header_content = "Basic " <> Base.encode64("#{username}:#{password}") -    put_req_header(conn, "authorization", header_content) -  end - -  describe "GET /api/search.json" do -    test "it returns search results", %{conn: conn} do -      user = insert(:user) -      user_two = insert(:user, %{nickname: "shp@shitposter.club"}) - -      {:ok, activity} = CommonAPI.post(user, %{"status" => "This is about 2hu"}) -      {:ok, _} = CommonAPI.post(user_two, %{"status" => "This isn't"}) - -      conn = -        conn -        |> get("/api/search.json", %{"q" => "2hu", "page" => "1", "rpp" => "1"}) - -      assert [status] = json_response(conn, 200) -      assert status["id"] == activity.id -    end -  end - -  describe "GET /api/statusnet/tags/timeline/:tag.json" do -    test "it returns the tags timeline", %{conn: conn} do -      user = insert(:user) -      user_two = insert(:user, %{nickname: "shp@shitposter.club"}) - -      {:ok, activity} = CommonAPI.post(user, %{"status" => "This is about #2hu"}) -      {:ok, _} = CommonAPI.post(user_two, %{"status" => "This isn't"}) - -      conn = -        conn -        |> get("/api/statusnet/tags/timeline/2hu.json") - -      assert [status] = json_response(conn, 200) -      assert status["id"] == activity.id -    end -  end - -  test "Convert newlines to <br> in bio", %{conn: conn} do -    user = insert(:user) - -    _conn = -      conn -      |> assign(:user, user) -      |> post("/api/account/update_profile.json", %{ -        "description" => "Hello,\r\nWorld! I\n am a test." -      }) - -    user = Repo.get!(User, user.id) -    assert user.bio == "Hello,<br>World! I<br> am a test." -  end - -  describe "POST /api/pleroma/change_password" do -    setup [:valid_user] - -    test "without credentials", %{conn: conn} do -      conn = post(conn, "/api/pleroma/change_password") -      assert json_response(conn, 403) == %{"error" => "Invalid credentials."} -    end - -    test "with credentials and invalid password", %{conn: conn, user: current_user} do -      conn = -        conn -        |> with_credentials(current_user.nickname, "test") -        |> post("/api/pleroma/change_password", %{ -          "password" => "hi", -          "new_password" => "newpass", -          "new_password_confirmation" => "newpass" -        }) - -      assert json_response(conn, 200) == %{"error" => "Invalid password."} -    end - -    test "with credentials, valid password and new password and confirmation not matching", %{ -      conn: conn, -      user: current_user -    } do -      conn = -        conn -        |> with_credentials(current_user.nickname, "test") -        |> post("/api/pleroma/change_password", %{ -          "password" => "test", -          "new_password" => "newpass", -          "new_password_confirmation" => "notnewpass" -        }) - -      assert json_response(conn, 200) == %{ -               "error" => "New password does not match confirmation." -             } -    end - -    test "with credentials, valid password and invalid new password", %{ -      conn: conn, -      user: current_user -    } do -      conn = -        conn -        |> with_credentials(current_user.nickname, "test") -        |> post("/api/pleroma/change_password", %{ -          "password" => "test", -          "new_password" => "", -          "new_password_confirmation" => "" -        }) - -      assert json_response(conn, 200) == %{ -               "error" => "New password can't be blank." -             } -    end - -    test "with credentials, valid password and matching new password and confirmation", %{ -      conn: conn, -      user: current_user -    } do -      conn = -        conn -        |> with_credentials(current_user.nickname, "test") -        |> post("/api/pleroma/change_password", %{ -          "password" => "test", -          "new_password" => "newpass", -          "new_password_confirmation" => "newpass" -        }) - -      assert json_response(conn, 200) == %{"status" => "success"} -      fetched_user = User.get_cached_by_id(current_user.id) -      assert Pbkdf2.checkpw("newpass", fetched_user.password_hash) == true -    end -  end - -  describe "POST /api/pleroma/delete_account" do -    setup [:valid_user] - -    test "without credentials", %{conn: conn} do -      conn = post(conn, "/api/pleroma/delete_account") -      assert json_response(conn, 403) == %{"error" => "Invalid credentials."} -    end - -    test "with credentials and invalid password", %{conn: conn, user: current_user} do -      conn = -        conn -        |> with_credentials(current_user.nickname, "test") -        |> post("/api/pleroma/delete_account", %{"password" => "hi"}) - -      assert json_response(conn, 200) == %{"error" => "Invalid password."} -    end - -    test "with credentials and valid password", %{conn: conn, user: current_user} do -      conn = -        conn -        |> with_credentials(current_user.nickname, "test") -        |> post("/api/pleroma/delete_account", %{"password" => "test"}) - -      assert json_response(conn, 200) == %{"status" => "success"} -      # Wait a second for the started task to end -      :timer.sleep(1000) -    end -  end - -  describe "GET /api/pleroma/friend_requests" do -    test "it lists friend requests" do -      user = insert(:user) -      other_user = insert(:user) - -      {:ok, _activity} = ActivityPub.follow(other_user, user) - -      user = User.get_cached_by_id(user.id) -      other_user = User.get_cached_by_id(other_user.id) - -      assert User.following?(other_user, user) == false - -      conn = -        build_conn() -        |> assign(:user, user) -        |> get("/api/pleroma/friend_requests") - -      assert [relationship] = json_response(conn, 200) -      assert other_user.id == relationship["id"] -    end - -    test "requires 'read' permission", %{conn: conn} do -      token1 = insert(:oauth_token, scopes: ["write"]) -      token2 = insert(:oauth_token, scopes: ["read"]) - -      for token <- [token1, token2] do -        conn = -          conn -          |> put_req_header("authorization", "Bearer #{token.token}") -          |> get("/api/pleroma/friend_requests") - -        if token == token1 do -          assert %{"error" => "Insufficient permissions: read."} == json_response(conn, 403) -        else -          assert json_response(conn, 200) -        end -      end -    end -  end - -  describe "POST /api/pleroma/friendships/approve" do -    test "it approves a friend request" do -      user = insert(:user) -      other_user = insert(:user) - -      {:ok, _activity} = ActivityPub.follow(other_user, user) - -      user = User.get_cached_by_id(user.id) -      other_user = User.get_cached_by_id(other_user.id) - -      assert User.following?(other_user, user) == false - -      conn = -        build_conn() -        |> assign(:user, user) -        |> post("/api/pleroma/friendships/approve", %{"user_id" => other_user.id}) - -      assert relationship = json_response(conn, 200) -      assert other_user.id == relationship["id"] -      assert relationship["follows_you"] == true -    end -  end - -  describe "POST /api/pleroma/friendships/deny" do -    test "it denies a friend request" do -      user = insert(:user) -      other_user = insert(:user) - -      {:ok, _activity} = ActivityPub.follow(other_user, user) - -      user = User.get_cached_by_id(user.id) -      other_user = User.get_cached_by_id(other_user.id) - -      assert User.following?(other_user, user) == false - -      conn = -        build_conn() -        |> assign(:user, user) -        |> post("/api/pleroma/friendships/deny", %{"user_id" => other_user.id}) - -      assert relationship = json_response(conn, 200) -      assert other_user.id == relationship["id"] -      assert relationship["follows_you"] == false -    end -  end - -  describe "GET /api/pleroma/search_user" do -    test "it returns users, ordered by similarity", %{conn: conn} do -      user = insert(:user, %{name: "eal"}) -      user_two = insert(:user, %{name: "eal me"}) -      _user_three = insert(:user, %{name: "zzz"}) - -      resp = -        conn -        |> get(twitter_api_search__path(conn, :search_user), query: "eal me") -        |> json_response(200) - -      assert length(resp) == 2 -      assert [user_two.id, user.id] == Enum.map(resp, fn %{"id" => id} -> id end) -    end -  end - -  describe "POST /api/media/upload" do -    setup context do -      Pleroma.DataCase.ensure_local_uploader(context) -    end - -    test "it performs the upload and sets `data[actor]` with AP id of uploader user", %{ -      conn: conn -    } do -      user = insert(:user) - -      upload_filename = "test/fixtures/image_tmp.jpg" -      File.cp!("test/fixtures/image.jpg", upload_filename) - -      file = %Plug.Upload{ -        content_type: "image/jpg", -        path: Path.absname(upload_filename), -        filename: "image.jpg" -      } - -      response = -        conn -        |> assign(:user, user) -        |> put_req_header("content-type", "application/octet-stream") -        |> post("/api/media/upload", %{ -          "media" => file -        }) -        |> json_response(:ok) - -      assert response["media_id"] -      object = Repo.get(Object, response["media_id"]) -      assert object -      assert object.data["actor"] == User.ap_id(user) -    end -  end - -  describe "POST /api/media/metadata/create" do -    setup do -      object = insert(:note) -      user = User.get_cached_by_ap_id(object.data["actor"]) -      %{object: object, user: user} -    end - -    test "it returns :forbidden status on attempt to modify someone else's upload", %{ -      conn: conn, -      object: object -    } do -      initial_description = object.data["name"] -      another_user = insert(:user) - -      conn -      |> assign(:user, another_user) -      |> post("/api/media/metadata/create", %{"media_id" => object.id}) -      |> json_response(:forbidden) - -      object = Repo.get(Object, object.id) -      assert object.data["name"] == initial_description -    end - -    test "it updates `data[name]` of referenced Object with provided value", %{ -      conn: conn, -      object: object, -      user: user -    } do -      description = "Informative description of the image. Initial value: #{object.data["name"]}}" - -      conn -      |> assign(:user, user) -      |> post("/api/media/metadata/create", %{ -        "media_id" => object.id, -        "alt_text" => %{"text" => description} -      }) -      |> json_response(:no_content) - -      object = Repo.get(Object, object.id) -      assert object.data["name"] == description -    end -  end - -  describe "POST /api/statuses/user_timeline.json?user_id=:user_id&pinned=true" do -    test "it returns a list of pinned statuses", %{conn: conn} do -      Pleroma.Config.put([:instance, :max_pinned_statuses], 1) - -      user = insert(:user, %{name: "egor"}) -      {:ok, %{id: activity_id}} = CommonAPI.post(user, %{"status" => "HI!!!"}) -      {:ok, _} = CommonAPI.pin(activity_id, user) - -      resp = -        conn -        |> get("/api/statuses/user_timeline.json", %{user_id: user.id, pinned: true}) -        |> json_response(200) - -      assert length(resp) == 1 -      assert [%{"id" => ^activity_id, "pinned" => true}] = resp -    end -  end - -  describe "POST /api/statuses/pin/:id" do -    setup do -      Pleroma.Config.put([:instance, :max_pinned_statuses], 1) -      [user: insert(:user)] -    end - -    test "without valid credentials", %{conn: conn} do -      note_activity = insert(:note_activity) -      conn = post(conn, "/api/statuses/pin/#{note_activity.id}.json") -      assert json_response(conn, 403) == %{"error" => "Invalid credentials."} -    end - -    test "with credentials", %{conn: conn, user: user} do -      {:ok, activity} = CommonAPI.post(user, %{"status" => "test!"}) - -      request_path = "/api/statuses/pin/#{activity.id}.json" - -      response = -        conn -        |> with_credentials(user.nickname, "test") -        |> post(request_path) - -      user = refresh_record(user) - -      assert json_response(response, 200) == -               ActivityView.render("activity.json", %{user: user, for: user, activity: activity}) -    end -  end - -  describe "POST /api/statuses/unpin/:id" do -    setup do -      Pleroma.Config.put([:instance, :max_pinned_statuses], 1) -      [user: insert(:user)] -    end - -    test "without valid credentials", %{conn: conn} do -      note_activity = insert(:note_activity) -      conn = post(conn, "/api/statuses/unpin/#{note_activity.id}.json") -      assert json_response(conn, 403) == %{"error" => "Invalid credentials."} -    end - -    test "with credentials", %{conn: conn, user: user} do -      {:ok, activity} = CommonAPI.post(user, %{"status" => "test!"}) -      {:ok, activity} = CommonAPI.pin(activity.id, user) - -      request_path = "/api/statuses/unpin/#{activity.id}.json" - -      response = -        conn -        |> with_credentials(user.nickname, "test") -        |> post(request_path) - -      user = refresh_record(user) - -      assert json_response(response, 200) == -               ActivityView.render("activity.json", %{user: user, for: user, activity: activity}) -    end -  end - -  describe "GET /api/oauth_tokens" do -    setup do -      token = insert(:oauth_token) |> Repo.preload(:user) - -      %{token: token} -    end - -    test "renders list", %{token: token} do -      response = -        build_conn() -        |> assign(:user, token.user) -        |> get("/api/oauth_tokens") - -      keys = -        json_response(response, 200) -        |> hd() -        |> Map.keys() - -      assert keys -- ["id", "app_name", "valid_until"] == [] -    end - -    test "revoke token", %{token: token} do -      response = -        build_conn() -        |> assign(:user, token.user) -        |> delete("/api/oauth_tokens/#{token.id}") - -      tokens = Token.get_user_tokens(token.user) - -      assert tokens == [] -      assert response.status == 201 -    end -  end -end diff --git a/test/web/twitter_api/twitter_api_test.exs b/test/web/twitter_api/twitter_api_test.exs index cbe83852e..85a9be3e0 100644 --- a/test/web/twitter_api/twitter_api_test.exs +++ b/test/web/twitter_api/twitter_api_test.exs @@ -1,273 +1,21 @@  # Pleroma: A lightweight social networking server -# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do    use Pleroma.DataCase -  alias Pleroma.Activity -  alias Pleroma.Object    alias Pleroma.Repo +  alias Pleroma.Tests.ObanHelpers    alias Pleroma.User    alias Pleroma.UserInviteToken -  alias Pleroma.Web.ActivityPub.ActivityPub -  alias Pleroma.Web.TwitterAPI.ActivityView +  alias Pleroma.Web.MastodonAPI.AccountView    alias Pleroma.Web.TwitterAPI.TwitterAPI -  alias Pleroma.Web.TwitterAPI.UserView - -  import Pleroma.Factory    setup_all do      Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)      :ok    end -  test "create a status" do -    user = insert(:user) -    mentioned_user = insert(:user, %{nickname: "shp", ap_id: "shp"}) - -    object_data = %{ -      "type" => "Image", -      "url" => [ -        %{ -          "type" => "Link", -          "mediaType" => "image/jpg", -          "href" => "http://example.org/image.jpg" -        } -      ], -      "uuid" => 1 -    } - -    object = Repo.insert!(%Object{data: object_data}) - -    input = %{ -      "status" => -        "Hello again, @shp.<script></script>\nThis is on another :firefox: line. #2hu #epic #phantasmagoric", -      "media_ids" => [object.id] -    } - -    {:ok, activity = %Activity{}} = TwitterAPI.create_status(user, input) -    object = Object.normalize(activity) - -    expected_text = -      "Hello again, <span class='h-card'><a data-user='#{mentioned_user.id}' class='u-url mention' href='shp'>@<span>shp</span></a></span>.<script></script><br>This is on another :firefox: line. <a class='hashtag' data-tag='2hu' href='http://localhost:4001/tag/2hu' rel='tag'>#2hu</a> <a class='hashtag' data-tag='epic' href='http://localhost:4001/tag/epic' rel='tag'>#epic</a> <a class='hashtag' data-tag='phantasmagoric' href='http://localhost:4001/tag/phantasmagoric' rel='tag'>#phantasmagoric</a><br><a href=\"http://example.org/image.jpg\" class='attachment'>image.jpg</a>" - -    assert get_in(object.data, ["content"]) == expected_text -    assert get_in(object.data, ["type"]) == "Note" -    assert get_in(object.data, ["actor"]) == user.ap_id -    assert get_in(activity.data, ["actor"]) == user.ap_id -    assert Enum.member?(get_in(activity.data, ["cc"]), User.ap_followers(user)) - -    assert Enum.member?( -             get_in(activity.data, ["to"]), -             "https://www.w3.org/ns/activitystreams#Public" -           ) - -    assert Enum.member?(get_in(activity.data, ["to"]), "shp") -    assert activity.local == true - -    assert %{"firefox" => "http://localhost:4001/emoji/Firefox.gif"} = object.data["emoji"] - -    # hashtags -    assert object.data["tag"] == ["2hu", "epic", "phantasmagoric"] - -    # Add a context -    assert is_binary(get_in(activity.data, ["context"])) -    assert is_binary(get_in(object.data, ["context"])) - -    assert is_list(object.data["attachment"]) - -    assert activity.data["object"] == object.data["id"] - -    user = User.get_cached_by_ap_id(user.ap_id) - -    assert user.info.note_count == 1 -  end - -  test "create a status that is a reply" do -    user = insert(:user) - -    input = %{ -      "status" => "Hello again." -    } - -    {:ok, activity = %Activity{}} = TwitterAPI.create_status(user, input) -    object = Object.normalize(activity) - -    input = %{ -      "status" => "Here's your (you).", -      "in_reply_to_status_id" => activity.id -    } - -    {:ok, reply = %Activity{}} = TwitterAPI.create_status(user, input) -    reply_object = Object.normalize(reply) - -    assert get_in(reply.data, ["context"]) == get_in(activity.data, ["context"]) - -    assert get_in(reply_object.data, ["context"]) == get_in(object.data, ["context"]) - -    assert get_in(reply_object.data, ["inReplyTo"]) == get_in(activity.data, ["object"]) -    assert Activity.get_in_reply_to_activity(reply).id == activity.id -  end - -  test "Follow another user using user_id" do -    user = insert(:user) -    followed = insert(:user) - -    {:ok, user, followed, _activity} = TwitterAPI.follow(user, %{"user_id" => followed.id}) -    assert User.ap_followers(followed) in user.following - -    {:ok, _, _, _} = TwitterAPI.follow(user, %{"user_id" => followed.id}) -  end - -  test "Follow another user using screen_name" do -    user = insert(:user) -    followed = insert(:user) - -    {:ok, user, followed, _activity} = -      TwitterAPI.follow(user, %{"screen_name" => followed.nickname}) - -    assert User.ap_followers(followed) in user.following - -    followed = User.get_cached_by_ap_id(followed.ap_id) -    assert followed.info.follower_count == 1 - -    {:ok, _, _, _} = TwitterAPI.follow(user, %{"screen_name" => followed.nickname}) -  end - -  test "Unfollow another user using user_id" do -    unfollowed = insert(:user) -    user = insert(:user, %{following: [User.ap_followers(unfollowed)]}) -    ActivityPub.follow(user, unfollowed) - -    {:ok, user, unfollowed} = TwitterAPI.unfollow(user, %{"user_id" => unfollowed.id}) -    assert user.following == [] - -    {:error, msg} = TwitterAPI.unfollow(user, %{"user_id" => unfollowed.id}) -    assert msg == "Not subscribed!" -  end - -  test "Unfollow another user using screen_name" do -    unfollowed = insert(:user) -    user = insert(:user, %{following: [User.ap_followers(unfollowed)]}) - -    ActivityPub.follow(user, unfollowed) - -    {:ok, user, unfollowed} = TwitterAPI.unfollow(user, %{"screen_name" => unfollowed.nickname}) -    assert user.following == [] - -    {:error, msg} = TwitterAPI.unfollow(user, %{"screen_name" => unfollowed.nickname}) -    assert msg == "Not subscribed!" -  end - -  test "Block another user using user_id" do -    user = insert(:user) -    blocked = insert(:user) - -    {:ok, user, blocked} = TwitterAPI.block(user, %{"user_id" => blocked.id}) -    assert User.blocks?(user, blocked) -  end - -  test "Block another user using screen_name" do -    user = insert(:user) -    blocked = insert(:user) - -    {:ok, user, blocked} = TwitterAPI.block(user, %{"screen_name" => blocked.nickname}) -    assert User.blocks?(user, blocked) -  end - -  test "Unblock another user using user_id" do -    unblocked = insert(:user) -    user = insert(:user) -    {:ok, user, _unblocked} = TwitterAPI.block(user, %{"user_id" => unblocked.id}) - -    {:ok, user, _unblocked} = TwitterAPI.unblock(user, %{"user_id" => unblocked.id}) -    assert user.info.blocks == [] -  end - -  test "Unblock another user using screen_name" do -    unblocked = insert(:user) -    user = insert(:user) -    {:ok, user, _unblocked} = TwitterAPI.block(user, %{"screen_name" => unblocked.nickname}) - -    {:ok, user, _unblocked} = TwitterAPI.unblock(user, %{"screen_name" => unblocked.nickname}) -    assert user.info.blocks == [] -  end - -  test "upload a file" do -    user = insert(:user) - -    file = %Plug.Upload{ -      content_type: "image/jpg", -      path: Path.absname("test/fixtures/image.jpg"), -      filename: "an_image.jpg" -    } - -    response = TwitterAPI.upload(file, user) - -    assert is_binary(response) -  end - -  test "it favorites a status, returns the updated activity" do -    user = insert(:user) -    other_user = insert(:user) -    note_activity = insert(:note_activity) - -    {:ok, status} = TwitterAPI.fav(user, note_activity.id) -    updated_activity = Activity.get_by_ap_id(note_activity.data["id"]) -    assert ActivityView.render("activity.json", %{activity: updated_activity})["fave_num"] == 1 - -    object = Object.normalize(note_activity) - -    assert object.data["like_count"] == 1 - -    assert status == updated_activity - -    {:ok, _status} = TwitterAPI.fav(other_user, note_activity.id) - -    object = Object.normalize(note_activity) - -    assert object.data["like_count"] == 2 - -    updated_activity = Activity.get_by_ap_id(note_activity.data["id"]) -    assert ActivityView.render("activity.json", %{activity: updated_activity})["fave_num"] == 2 -  end - -  test "it unfavorites a status, returns the updated activity" do -    user = insert(:user) -    note_activity = insert(:note_activity) -    object = Object.normalize(note_activity) - -    {:ok, _like_activity, _object} = ActivityPub.like(user, object) -    updated_activity = Activity.get_by_ap_id(note_activity.data["id"]) - -    assert ActivityView.render("activity.json", activity: updated_activity)["fave_num"] == 1 - -    {:ok, activity} = TwitterAPI.unfav(user, note_activity.id) - -    assert ActivityView.render("activity.json", activity: activity)["fave_num"] == 0 -  end - -  test "it retweets a status and returns the retweet" do -    user = insert(:user) -    note_activity = insert(:note_activity) - -    {:ok, status} = TwitterAPI.repeat(user, note_activity.id) -    updated_activity = Activity.get_by_ap_id(note_activity.data["id"]) - -    assert status == updated_activity -  end - -  test "it unretweets an already retweeted status" do -    user = insert(:user) -    note_activity = insert(:note_activity) - -    {:ok, _status} = TwitterAPI.repeat(user, note_activity.id) -    {:ok, status} = TwitterAPI.unrepeat(user, note_activity.id) -    updated_activity = Activity.get_by_ap_id(note_activity.data["id"]) - -    assert status == updated_activity -  end -    test "it registers a new user and returns the user." do      data = %{        "nickname" => "lain", @@ -281,8 +29,8 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do      fetched_user = User.get_cached_by_nickname("lain") -    assert UserView.render("show.json", %{user: user}) == -             UserView.render("show.json", %{user: fetched_user}) +    assert AccountView.render("show.json", %{user: user}) == +             AccountView.render("show.json", %{user: fetched_user})    end    test "it registers a new user with empty string in bio and returns the user." do @@ -299,8 +47,8 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do      fetched_user = User.get_cached_by_nickname("lain") -    assert UserView.render("show.json", %{user: user}) == -             UserView.render("show.json", %{user: fetched_user}) +    assert AccountView.render("show.json", %{user: user}) == +             AccountView.render("show.json", %{user: fetched_user})    end    test "it sends confirmation email if :account_activation_required is specified in instance config" do @@ -321,8 +69,9 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do      }      {:ok, user} = TwitterAPI.register_user(data) +    ObanHelpers.perform_all() -    assert user.info.confirmation_pending +    assert user.confirmation_pending      email = Pleroma.Emails.UserEmail.account_confirmation_email(user) @@ -360,7 +109,9 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do      {:ok, user2} = TwitterAPI.register_user(data2)      expected_text = -      "<span class='h-card'><a data-user='#{user1.id}' class='u-url mention' href='#{user1.ap_id}'>@<span>john</span></a></span> test" +      ~s(<span class="h-card"><a data-user="#{user1.id}" class="u-url mention" href="#{ +        user1.ap_id +      }" rel="ugc">@<span>john</span></a></span> test)      assert user2.bio == expected_text    end @@ -397,8 +148,8 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do        assert invite.used == true -      assert UserView.render("show.json", %{user: user}) == -               UserView.render("show.json", %{user: fetched_user}) +      assert AccountView.render("show.json", %{user: user}) == +               AccountView.render("show.json", %{user: fetched_user})      end      test "returns error on invalid token" do @@ -462,8 +213,8 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do          {:ok, user} = TwitterAPI.register_user(data)          fetched_user = User.get_cached_by_nickname("vinny") -        assert UserView.render("show.json", %{user: user}) == -                 UserView.render("show.json", %{user: fetched_user}) +        assert AccountView.render("show.json", %{user: user}) == +                 AccountView.render("show.json", %{user: fetched_user})        end        {:ok, data: data, check_fn: check_fn} @@ -537,8 +288,8 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do        assert invite.used == true -      assert UserView.render("show.json", %{user: user}) == -               UserView.render("show.json", %{user: fetched_user}) +      assert AccountView.render("show.json", %{user: user}) == +               AccountView.render("show.json", %{user: fetched_user})        data = %{          "nickname" => "GrimReaper", @@ -588,8 +339,8 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do        refute invite.used -      assert UserView.render("show.json", %{user: user}) == -               UserView.render("show.json", %{user: fetched_user}) +      assert AccountView.render("show.json", %{user: user}) == +               AccountView.render("show.json", %{user: fetched_user})      end      test "error after max uses" do @@ -612,8 +363,8 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do        invite = Repo.get_by(UserInviteToken, token: invite.token)        assert invite.used == true -      assert UserView.render("show.json", %{user: user}) == -               UserView.render("show.json", %{user: fetched_user}) +      assert AccountView.render("show.json", %{user: user}) == +               AccountView.render("show.json", %{user: fetched_user})        data = %{          "nickname" => "GrimReaper", @@ -689,31 +440,9 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do      refute User.get_cached_by_nickname("lain")    end -  test "it assigns an integer conversation_id" do -    note_activity = insert(:note_activity) -    status = ActivityView.render("activity.json", activity: note_activity) - -    assert is_number(status["statusnet_conversation_id"]) -  end -    setup do      Supervisor.terminate_child(Pleroma.Supervisor, Cachex)      Supervisor.restart_child(Pleroma.Supervisor, Cachex)      :ok    end - -  describe "fetching a user by uri" do -    test "fetches a user by uri" do -      id = "https://mastodon.social/users/lambadalambda" -      user = insert(:user) -      {:ok, represented} = TwitterAPI.get_external_profile(user, id) -      remote = User.get_cached_by_ap_id(id) - -      assert represented["id"] == UserView.render("show.json", %{user: remote, for: user})["id"] - -      # Also fetches the feed. -      # assert Activity.get_create_by_object_ap_id("tag:mastodon.social,2017-04-05:objectId=1641750:objectType=Status") -      # credo:disable-for-previous-line Credo.Check.Readability.MaxLineLength -    end -  end  end diff --git a/test/web/twitter_api/util_controller_test.exs b/test/web/twitter_api/util_controller_test.exs index 3d699e1df..5d60c0d51 100644 --- a/test/web/twitter_api/util_controller_test.exs +++ b/test/web/twitter_api/util_controller_test.exs @@ -4,11 +4,11 @@  defmodule Pleroma.Web.TwitterAPI.UtilControllerTest do    use Pleroma.Web.ConnCase +  use Oban.Testing, repo: Pleroma.Repo -  alias Pleroma.Notification -  alias Pleroma.Repo +  alias Pleroma.Tests.ObanHelpers    alias Pleroma.User -  alias Pleroma.Web.CommonAPI +    import Pleroma.Factory    import Mock @@ -17,27 +17,56 @@ defmodule Pleroma.Web.TwitterAPI.UtilControllerTest do      :ok    end +  clear_config([:instance]) +  clear_config([:frontend_configurations, :pleroma_fe]) +  clear_config([:user, :deny_follow_blocked]) +    describe "POST /api/pleroma/follow_import" do +    setup do: oauth_access(["follow"]) +      test "it returns HTTP 200", %{conn: conn} do -      user1 = insert(:user)        user2 = insert(:user)        response =          conn -        |> assign(:user, user1)          |> post("/api/pleroma/follow_import", %{"list" => "#{user2.ap_id}"})          |> json_response(:ok)        assert response == "job started"      end +    test "it imports follow lists from file", %{user: user1, conn: conn} do +      user2 = insert(:user) + +      with_mocks([ +        {File, [], +         read!: fn "follow_list.txt" -> +           "Account address,Show boosts\n#{user2.ap_id},true" +         end} +      ]) do +        response = +          conn +          |> post("/api/pleroma/follow_import", %{"list" => %Plug.Upload{path: "follow_list.txt"}}) +          |> json_response(:ok) + +        assert response == "job started" + +        assert ObanHelpers.member?( +                 %{ +                   "op" => "follow_import", +                   "follower_id" => user1.id, +                   "followed_identifiers" => [user2.ap_id] +                 }, +                 all_enqueued(worker: Pleroma.Workers.BackgroundWorker) +               ) +      end +    end +      test "it imports new-style mastodon follow lists", %{conn: conn} do -      user1 = insert(:user)        user2 = insert(:user)        response =          conn -        |> assign(:user, user1)          |> post("/api/pleroma/follow_import", %{            "list" => "Account address,Show boosts\n#{user2.ap_id},true"          }) @@ -46,19 +75,21 @@ defmodule Pleroma.Web.TwitterAPI.UtilControllerTest do        assert response == "job started"      end -    test "requires 'follow' permission", %{conn: conn} do +    test "requires 'follow' or 'write:follows' permissions" do        token1 = insert(:oauth_token, scopes: ["read", "write"])        token2 = insert(:oauth_token, scopes: ["follow"]) +      token3 = insert(:oauth_token, scopes: ["something"])        another_user = insert(:user) -      for token <- [token1, token2] do +      for token <- [token1, token2, token3] do          conn = -          conn +          build_conn()            |> put_req_header("authorization", "Bearer #{token.token}")            |> post("/api/pleroma/follow_import", %{"list" => "#{another_user.ap_id}"}) -        if token == token1 do -          assert %{"error" => "Insufficient permissions: follow."} == json_response(conn, 403) +        if token == token3 do +          assert %{"error" => "Insufficient permissions: follow | write:follows."} == +                   json_response(conn, 403)          else            assert json_response(conn, 200)          end @@ -67,65 +98,143 @@ defmodule Pleroma.Web.TwitterAPI.UtilControllerTest do    end    describe "POST /api/pleroma/blocks_import" do +    # Note: "follow" or "write:blocks" permission is required +    setup do: oauth_access(["write:blocks"]) +      test "it returns HTTP 200", %{conn: conn} do -      user1 = insert(:user)        user2 = insert(:user)        response =          conn -        |> assign(:user, user1)          |> post("/api/pleroma/blocks_import", %{"list" => "#{user2.ap_id}"})          |> json_response(:ok)        assert response == "job started"      end -  end -  describe "POST /api/pleroma/notifications/read" do -    test "it marks a single notification as read", %{conn: conn} do -      user1 = insert(:user) +    test "it imports blocks users from file", %{user: user1, conn: conn} do        user2 = insert(:user) -      {:ok, activity1} = CommonAPI.post(user2, %{"status" => "hi @#{user1.nickname}"}) -      {:ok, activity2} = CommonAPI.post(user2, %{"status" => "hi @#{user1.nickname}"}) -      {:ok, [notification1]} = Notification.create_notifications(activity1) -      {:ok, [notification2]} = Notification.create_notifications(activity2) - -      conn -      |> assign(:user, user1) -      |> post("/api/pleroma/notifications/read", %{"id" => "#{notification1.id}"}) -      |> json_response(:ok) +      user3 = insert(:user) -      assert Repo.get(Notification, notification1.id).seen -      refute Repo.get(Notification, notification2.id).seen +      with_mocks([ +        {File, [], read!: fn "blocks_list.txt" -> "#{user2.ap_id} #{user3.ap_id}" end} +      ]) do +        response = +          conn +          |> post("/api/pleroma/blocks_import", %{"list" => %Plug.Upload{path: "blocks_list.txt"}}) +          |> json_response(:ok) + +        assert response == "job started" + +        assert ObanHelpers.member?( +                 %{ +                   "op" => "blocks_import", +                   "blocker_id" => user1.id, +                   "blocked_identifiers" => [user2.ap_id, user3.ap_id] +                 }, +                 all_enqueued(worker: Pleroma.Workers.BackgroundWorker) +               ) +      end      end    end    describe "PUT /api/pleroma/notification_settings" do -    test "it updates notification settings", %{conn: conn} do -      user = insert(:user) +    setup do: oauth_access(["write:accounts"]) +    test "it updates notification settings", %{user: user, conn: conn} do        conn -      |> assign(:user, user)        |> put("/api/pleroma/notification_settings", %{          "followers" => false,          "bar" => 1        })        |> json_response(:ok) -      user = Repo.get(User, user.id) +      user = refresh_record(user) + +      assert %Pleroma.User.NotificationSetting{ +               followers: false, +               follows: true, +               non_follows: true, +               non_followers: true, +               privacy_option: false +             } == user.notification_settings +    end + +    test "it updates notification privacy option", %{user: user, conn: conn} do +      conn +      |> put("/api/pleroma/notification_settings", %{"privacy_option" => "1"}) +      |> json_response(:ok) + +      user = refresh_record(user) -      assert %{ -               "followers" => false, -               "follows" => true, -               "non_follows" => true, -               "non_followers" => true -             } == user.info.notification_settings +      assert %Pleroma.User.NotificationSetting{ +               followers: true, +               follows: true, +               non_follows: true, +               non_followers: true, +               privacy_option: true +             } == user.notification_settings      end    end -  describe "GET /api/statusnet/config.json" do +  describe "GET /api/statusnet/config" do +    test "it returns config in xml format", %{conn: conn} do +      instance = Pleroma.Config.get(:instance) + +      response = +        conn +        |> put_req_header("accept", "application/xml") +        |> get("/api/statusnet/config") +        |> response(:ok) + +      assert response == +               "<config>\n<site>\n<name>#{Keyword.get(instance, :name)}</name>\n<site>#{ +                 Pleroma.Web.base_url() +               }</site>\n<textlimit>#{Keyword.get(instance, :limit)}</textlimit>\n<closed>#{ +                 !Keyword.get(instance, :registrations_open) +               }</closed>\n</site>\n</config>\n" +    end + +    test "it returns config in json format", %{conn: conn} do +      instance = Pleroma.Config.get(:instance) +      Pleroma.Config.put([:instance, :managed_config], true) +      Pleroma.Config.put([:instance, :registrations_open], false) +      Pleroma.Config.put([:instance, :invites_enabled], true) +      Pleroma.Config.put([:instance, :public], false) +      Pleroma.Config.put([:frontend_configurations, :pleroma_fe], %{theme: "asuka-hospital"}) + +      response = +        conn +        |> put_req_header("accept", "application/json") +        |> get("/api/statusnet/config") +        |> json_response(:ok) + +      expected_data = %{ +        "site" => %{ +          "accountActivationRequired" => "0", +          "closed" => "1", +          "description" => Keyword.get(instance, :description), +          "invitesEnabled" => "1", +          "name" => Keyword.get(instance, :name), +          "pleromafe" => %{"theme" => "asuka-hospital"}, +          "private" => "1", +          "safeDMMentionsEnabled" => "0", +          "server" => Pleroma.Web.base_url(), +          "textlimit" => to_string(Keyword.get(instance, :limit)), +          "uploadlimit" => %{ +            "avatarlimit" => to_string(Keyword.get(instance, :avatar_upload_limit)), +            "backgroundlimit" => to_string(Keyword.get(instance, :background_upload_limit)), +            "bannerlimit" => to_string(Keyword.get(instance, :banner_upload_limit)), +            "uploadlimit" => to_string(Keyword.get(instance, :upload_limit)) +          }, +          "vapidPublicKey" => Keyword.get(Pleroma.Web.Push.vapid_config(), :public_key) +        } +      } + +      assert response == expected_data +    end +      test "returns the state of safe_dm_mentions flag", %{conn: conn} do -      option = Pleroma.Config.get([:instance, :safe_dm_mentions])        Pleroma.Config.put([:instance, :safe_dm_mentions], true)        response = @@ -143,8 +252,6 @@ defmodule Pleroma.Web.TwitterAPI.UtilControllerTest do          |> json_response(:ok)        assert response["site"]["safeDMMentionsEnabled"] == "0" - -      Pleroma.Config.put([:instance, :safe_dm_mentions], option)      end      test "it returns the managed config", %{conn: conn} do @@ -210,38 +317,8 @@ defmodule Pleroma.Web.TwitterAPI.UtilControllerTest do      end    end -  describe "GET /ostatus_subscribe?acct=...." do -    test "adds status to pleroma instance if the `acct` is a status", %{conn: conn} do -      conn = -        get( -          conn, -          "/ostatus_subscribe?acct=https://mastodon.social/users/emelie/statuses/101849165031453009" -        ) - -      assert redirected_to(conn) =~ "/notice/" -    end - -    test "show follow account page if the `acct` is a account link", %{conn: conn} do -      response = -        get( -          conn, -          "/ostatus_subscribe?acct=https://mastodon.social/users/emelie" -        ) - -      assert html_response(response, 200) =~ "Log in to follow" -    end -  end -    describe "GET /api/pleroma/healthcheck" do -    setup do -      config_healthcheck = Pleroma.Config.get([:instance, :healthcheck]) - -      on_exit(fn -> -        Pleroma.Config.put([:instance, :healthcheck], config_healthcheck) -      end) - -      :ok -    end +    clear_config([:instance, :healthcheck])      test "returns 503 when healthcheck disabled", %{conn: conn} do        Pleroma.Config.put([:instance, :healthcheck], false) @@ -274,7 +351,7 @@ defmodule Pleroma.Web.TwitterAPI.UtilControllerTest do        end      end -    test "returns 503 when healthcheck enabled and  health is false", %{conn: conn} do +    test "returns 503 when healthcheck enabled and health is false", %{conn: conn} do        Pleroma.Config.put([:instance, :healthcheck], true)        with_mock Pleroma.Healthcheck, @@ -296,20 +373,301 @@ defmodule Pleroma.Web.TwitterAPI.UtilControllerTest do    end    describe "POST /api/pleroma/disable_account" do -    test "it returns HTTP 200", %{conn: conn} do -      user = insert(:user) +    setup do: oauth_access(["write:accounts"]) +    test "with valid permissions and password, it disables the account", %{conn: conn, user: user} do        response =          conn -        |> assign(:user, user)          |> post("/api/pleroma/disable_account", %{"password" => "test"})          |> json_response(:ok)        assert response == %{"status" => "success"} +      ObanHelpers.perform_all() + +      user = User.get_cached_by_id(user.id) + +      assert user.deactivated == true +    end + +    test "with valid permissions and invalid password, it returns an error", %{conn: conn} do +      user = insert(:user) + +      response = +        conn +        |> post("/api/pleroma/disable_account", %{"password" => "test1"}) +        |> json_response(:ok) +      assert response == %{"error" => "Invalid password."}        user = User.get_cached_by_id(user.id) -      assert user.info.deactivated == true +      refute user.deactivated +    end +  end + +  describe "GET /api/statusnet/version" do +    test "it returns version in xml format", %{conn: conn} do +      response = +        conn +        |> put_req_header("accept", "application/xml") +        |> get("/api/statusnet/version") +        |> response(:ok) + +      assert response == "<version>#{Pleroma.Application.named_version()}</version>" +    end + +    test "it returns version in json format", %{conn: conn} do +      response = +        conn +        |> put_req_header("accept", "application/json") +        |> get("/api/statusnet/version") +        |> json_response(:ok) + +      assert response == "#{Pleroma.Application.named_version()}" +    end +  end + +  describe "POST /main/ostatus - remote_subscribe/2" do +    test "renders subscribe form", %{conn: conn} do +      user = insert(:user) + +      response = +        conn +        |> post("/main/ostatus", %{"nickname" => user.nickname, "profile" => ""}) +        |> response(:ok) + +      refute response =~ "Could not find user" +      assert response =~ "Remotely follow #{user.nickname}" +    end + +    test "renders subscribe form with error when user not found", %{conn: conn} do +      response = +        conn +        |> post("/main/ostatus", %{"nickname" => "nickname", "profile" => ""}) +        |> response(:ok) + +      assert response =~ "Could not find user" +      refute response =~ "Remotely follow" +    end + +    test "it redirect to webfinger url", %{conn: conn} do +      user = insert(:user) +      user2 = insert(:user, ap_id: "shp@social.heldscal.la") + +      conn = +        conn +        |> post("/main/ostatus", %{ +          "user" => %{"nickname" => user.nickname, "profile" => user2.ap_id} +        }) + +      assert redirected_to(conn) == +               "https://social.heldscal.la/main/ostatussub?profile=#{user.ap_id}" +    end + +    test "it renders form with error when user not found", %{conn: conn} do +      user2 = insert(:user, ap_id: "shp@social.heldscal.la") + +      response = +        conn +        |> post("/main/ostatus", %{"user" => %{"nickname" => "jimm", "profile" => user2.ap_id}}) +        |> response(:ok) + +      assert response =~ "Something went wrong." +    end +  end + +  test "it returns new captcha", %{conn: conn} do +    with_mock Pleroma.Captcha, +      new: fn -> "test_captcha" end do +      resp = +        conn +        |> get("/api/pleroma/captcha") +        |> response(200) + +      assert resp == "\"test_captcha\"" +      assert called(Pleroma.Captcha.new()) +    end +  end + +  describe "POST /api/pleroma/change_email" do +    setup do: oauth_access(["write:accounts"]) + +    test "without permissions", %{conn: conn} do +      conn = +        conn +        |> assign(:token, nil) +        |> post("/api/pleroma/change_email") + +      assert json_response(conn, 403) == %{"error" => "Insufficient permissions: write:accounts."} +    end + +    test "with proper permissions and invalid password", %{conn: conn} do +      conn = +        post(conn, "/api/pleroma/change_email", %{ +          "password" => "hi", +          "email" => "test@test.com" +        }) + +      assert json_response(conn, 200) == %{"error" => "Invalid password."} +    end + +    test "with proper permissions, valid password and invalid email", %{ +      conn: conn +    } do +      conn = +        post(conn, "/api/pleroma/change_email", %{ +          "password" => "test", +          "email" => "foobar" +        }) + +      assert json_response(conn, 200) == %{"error" => "Email has invalid format."} +    end + +    test "with proper permissions, valid password and no email", %{ +      conn: conn +    } do +      conn = +        post(conn, "/api/pleroma/change_email", %{ +          "password" => "test" +        }) + +      assert json_response(conn, 200) == %{"error" => "Email can't be blank."} +    end + +    test "with proper permissions, valid password and blank email", %{ +      conn: conn +    } do +      conn = +        post(conn, "/api/pleroma/change_email", %{ +          "password" => "test", +          "email" => "" +        }) + +      assert json_response(conn, 200) == %{"error" => "Email can't be blank."} +    end + +    test "with proper permissions, valid password and non unique email", %{ +      conn: conn +    } do +      user = insert(:user) + +      conn = +        post(conn, "/api/pleroma/change_email", %{ +          "password" => "test", +          "email" => user.email +        }) + +      assert json_response(conn, 200) == %{"error" => "Email has already been taken."} +    end + +    test "with proper permissions, valid password and valid email", %{ +      conn: conn +    } do +      conn = +        post(conn, "/api/pleroma/change_email", %{ +          "password" => "test", +          "email" => "cofe@foobar.com" +        }) + +      assert json_response(conn, 200) == %{"status" => "success"} +    end +  end + +  describe "POST /api/pleroma/change_password" do +    setup do: oauth_access(["write:accounts"]) + +    test "without permissions", %{conn: conn} do +      conn = +        conn +        |> assign(:token, nil) +        |> post("/api/pleroma/change_password") + +      assert json_response(conn, 403) == %{"error" => "Insufficient permissions: write:accounts."} +    end + +    test "with proper permissions and invalid password", %{conn: conn} do +      conn = +        post(conn, "/api/pleroma/change_password", %{ +          "password" => "hi", +          "new_password" => "newpass", +          "new_password_confirmation" => "newpass" +        }) + +      assert json_response(conn, 200) == %{"error" => "Invalid password."} +    end + +    test "with proper permissions, valid password and new password and confirmation not matching", +         %{ +           conn: conn +         } do +      conn = +        post(conn, "/api/pleroma/change_password", %{ +          "password" => "test", +          "new_password" => "newpass", +          "new_password_confirmation" => "notnewpass" +        }) + +      assert json_response(conn, 200) == %{ +               "error" => "New password does not match confirmation." +             } +    end + +    test "with proper permissions, valid password and invalid new password", %{ +      conn: conn +    } do +      conn = +        post(conn, "/api/pleroma/change_password", %{ +          "password" => "test", +          "new_password" => "", +          "new_password_confirmation" => "" +        }) + +      assert json_response(conn, 200) == %{ +               "error" => "New password can't be blank." +             } +    end + +    test "with proper permissions, valid password and matching new password and confirmation", %{ +      conn: conn, +      user: user +    } do +      conn = +        post(conn, "/api/pleroma/change_password", %{ +          "password" => "test", +          "new_password" => "newpass", +          "new_password_confirmation" => "newpass" +        }) + +      assert json_response(conn, 200) == %{"status" => "success"} +      fetched_user = User.get_cached_by_id(user.id) +      assert Comeonin.Pbkdf2.checkpw("newpass", fetched_user.password_hash) == true +    end +  end + +  describe "POST /api/pleroma/delete_account" do +    setup do: oauth_access(["write:accounts"]) + +    test "without permissions", %{conn: conn} do +      conn = +        conn +        |> assign(:token, nil) +        |> post("/api/pleroma/delete_account") + +      assert json_response(conn, 403) == +               %{"error" => "Insufficient permissions: write:accounts."} +    end + +    test "with proper permissions and wrong or missing password", %{conn: conn} do +      for params <- [%{"password" => "hi"}, %{}] do +        ret_conn = post(conn, "/api/pleroma/delete_account", params) + +        assert json_response(ret_conn, 200) == %{"error" => "Invalid password."} +      end +    end + +    test "with proper permissions and valid password", %{conn: conn} do +      conn = post(conn, "/api/pleroma/delete_account", %{"password" => "test"}) + +      assert json_response(conn, 200) == %{"status" => "success"}      end    end  end diff --git a/test/web/twitter_api/views/activity_view_test.exs b/test/web/twitter_api/views/activity_view_test.exs deleted file mode 100644 index 56d861efb..000000000 --- a/test/web/twitter_api/views/activity_view_test.exs +++ /dev/null @@ -1,384 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Web.TwitterAPI.ActivityViewTest do -  use Pleroma.DataCase - -  alias Pleroma.Activity -  alias Pleroma.Object -  alias Pleroma.Repo -  alias Pleroma.User -  alias Pleroma.Web.ActivityPub.ActivityPub -  alias Pleroma.Web.CommonAPI -  alias Pleroma.Web.CommonAPI.Utils -  alias Pleroma.Web.TwitterAPI.ActivityView -  alias Pleroma.Web.TwitterAPI.UserView - -  import Pleroma.Factory -  import Tesla.Mock - -  setup do -    mock(fn env -> apply(HttpRequestMock, :request, [env]) end) -    :ok -  end - -  import Mock - -  test "returns a temporary ap_id based user for activities missing db users" do -    user = insert(:user) - -    {:ok, activity} = CommonAPI.post(user, %{"status" => "Hey @shp!", "visibility" => "direct"}) - -    Repo.delete(user) -    Cachex.clear(:user_cache) - -    %{"user" => tw_user} = ActivityView.render("activity.json", activity: activity) - -    assert tw_user["screen_name"] == "erroruser@example.com" -    assert tw_user["name"] == user.ap_id -    assert tw_user["statusnet_profile_url"] == user.ap_id -  end - -  test "tries to get a user by nickname if fetching by ap_id doesn't work" do -    user = insert(:user) - -    {:ok, activity} = CommonAPI.post(user, %{"status" => "Hey @shp!", "visibility" => "direct"}) - -    {:ok, user} = -      user -      |> Ecto.Changeset.change(%{ap_id: "#{user.ap_id}/extension/#{user.nickname}"}) -      |> Repo.update() - -    Cachex.clear(:user_cache) - -    result = ActivityView.render("activity.json", activity: activity) -    assert result["user"]["id"] == user.id -  end - -  test "tells if the message is muted for some reason" do -    user = insert(:user) -    other_user = insert(:user) - -    {:ok, user} = User.mute(user, other_user) - -    {:ok, activity} = CommonAPI.post(other_user, %{"status" => "test"}) -    status = ActivityView.render("activity.json", %{activity: activity}) - -    assert status["muted"] == false - -    status = ActivityView.render("activity.json", %{activity: activity, for: user}) - -    assert status["muted"] == true -  end - -  test "a create activity with a html status" do -    text = """ -    #Bike log - Commute Tuesday\nhttps://pla.bike/posts/20181211/\n#cycling #CHScycling #commute\nMVIMG_20181211_054020.jpg -    """ - -    {:ok, activity} = CommonAPI.post(insert(:user), %{"status" => text}) - -    result = ActivityView.render("activity.json", activity: activity) - -    assert result["statusnet_html"] == -             "<a class=\"hashtag\" data-tag=\"bike\" href=\"http://localhost:4001/tag/bike\" rel=\"tag\">#Bike</a> log - Commute Tuesday<br /><a href=\"https://pla.bike/posts/20181211/\">https://pla.bike/posts/20181211/</a><br /><a class=\"hashtag\" data-tag=\"cycling\" href=\"http://localhost:4001/tag/cycling\" rel=\"tag\">#cycling</a> <a class=\"hashtag\" data-tag=\"chscycling\" href=\"http://localhost:4001/tag/chscycling\" rel=\"tag\">#CHScycling</a> <a class=\"hashtag\" data-tag=\"commute\" href=\"http://localhost:4001/tag/commute\" rel=\"tag\">#commute</a><br />MVIMG_20181211_054020.jpg" - -    assert result["text"] == -             "#Bike log - Commute Tuesday\nhttps://pla.bike/posts/20181211/\n#cycling #CHScycling #commute\nMVIMG_20181211_054020.jpg" -  end - -  test "a create activity with a summary containing emoji" do -    {:ok, activity} = -      CommonAPI.post(insert(:user), %{ -        "spoiler_text" => ":firefox: meow", -        "status" => "." -      }) - -    result = ActivityView.render("activity.json", activity: activity) - -    expected = ":firefox: meow" - -    expected_html = -      "<img class=\"emoji\" alt=\"firefox\" title=\"firefox\" src=\"http://localhost:4001/emoji/Firefox.gif\" /> meow" - -    assert result["summary"] == expected -    assert result["summary_html"] == expected_html -  end - -  test "a create activity with a summary containing invalid HTML" do -    {:ok, activity} = -      CommonAPI.post(insert(:user), %{ -        "spoiler_text" => "<span style=\"color: magenta; font-size: 32px;\">meow</span>", -        "status" => "." -      }) - -    result = ActivityView.render("activity.json", activity: activity) - -    expected = "meow" - -    assert result["summary"] == expected -    assert result["summary_html"] == expected -  end - -  test "a create activity with a note" do -    user = insert(:user) -    other_user = insert(:user, %{nickname: "shp"}) - -    {:ok, activity} = CommonAPI.post(user, %{"status" => "Hey @shp!", "visibility" => "direct"}) -    object = Object.normalize(activity) - -    result = ActivityView.render("activity.json", activity: activity) - -    convo_id = Utils.context_to_conversation_id(object.data["context"]) - -    expected = %{ -      "activity_type" => "post", -      "attachments" => [], -      "attentions" => [ -        UserView.render("show.json", %{user: other_user}) -      ], -      "created_at" => object.data["published"] |> Utils.date_to_asctime(), -      "external_url" => object.data["id"], -      "fave_num" => 0, -      "favorited" => false, -      "id" => activity.id, -      "in_reply_to_status_id" => nil, -      "in_reply_to_screen_name" => nil, -      "in_reply_to_user_id" => nil, -      "in_reply_to_profileurl" => nil, -      "in_reply_to_ostatus_uri" => nil, -      "is_local" => true, -      "is_post_verb" => true, -      "possibly_sensitive" => false, -      "repeat_num" => 0, -      "repeated" => false, -      "pinned" => false, -      "statusnet_conversation_id" => convo_id, -      "summary" => "", -      "summary_html" => "", -      "statusnet_html" => -        "Hey <span class=\"h-card\"><a data-user=\"#{other_user.id}\" class=\"u-url mention\" href=\"#{ -          other_user.ap_id -        }\">@<span>shp</span></a></span>!", -      "tags" => [], -      "text" => "Hey @shp!", -      "uri" => object.data["id"], -      "user" => UserView.render("show.json", %{user: user}), -      "visibility" => "direct", -      "card" => nil, -      "muted" => false -    } - -    assert result == expected -  end - -  test "a list of activities" do -    user = insert(:user) -    other_user = insert(:user, %{nickname: "shp"}) -    {:ok, activity} = CommonAPI.post(user, %{"status" => "Hey @shp!"}) -    object = Object.normalize(activity) - -    convo_id = Utils.context_to_conversation_id(object.data["context"]) - -    mocks = [ -      { -        Utils, -        [:passthrough], -        [context_to_conversation_id: fn _ -> false end] -      }, -      { -        User, -        [:passthrough], -        [get_cached_by_ap_id: fn _ -> nil end] -      } -    ] - -    with_mocks mocks do -      [result] = ActivityView.render("index.json", activities: [activity]) - -      assert result["statusnet_conversation_id"] == convo_id -      assert result["user"] -      refute called(Utils.context_to_conversation_id(:_)) -      refute called(User.get_cached_by_ap_id(user.ap_id)) -      refute called(User.get_cached_by_ap_id(other_user.ap_id)) -    end -  end - -  test "an activity that is a reply" do -    user = insert(:user) -    other_user = insert(:user, %{nickname: "shp"}) - -    {:ok, activity} = CommonAPI.post(user, %{"status" => "Hey @shp!"}) - -    {:ok, answer} = -      CommonAPI.post(other_user, %{"status" => "Hi!", "in_reply_to_status_id" => activity.id}) - -    result = ActivityView.render("activity.json", %{activity: answer}) - -    assert result["in_reply_to_status_id"] == activity.id -  end - -  test "a like activity" do -    user = insert(:user) -    other_user = insert(:user, %{nickname: "shp"}) - -    {:ok, activity} = CommonAPI.post(user, %{"status" => "Hey @shp!"}) -    {:ok, like, _object} = CommonAPI.favorite(activity.id, other_user) - -    result = ActivityView.render("activity.json", activity: like) -    activity = Pleroma.Activity.get_by_ap_id(activity.data["id"]) - -    expected = %{ -      "activity_type" => "like", -      "created_at" => like.data["published"] |> Utils.date_to_asctime(), -      "external_url" => like.data["id"], -      "id" => like.id, -      "in_reply_to_status_id" => activity.id, -      "is_local" => true, -      "is_post_verb" => false, -      "favorited_status" => ActivityView.render("activity.json", activity: activity), -      "statusnet_html" => "shp favorited a status.", -      "text" => "shp favorited a status.", -      "uri" => "tag:#{like.data["id"]}:objectType=Favourite", -      "user" => UserView.render("show.json", user: other_user) -    } - -    assert result == expected -  end - -  test "a like activity for deleted post" do -    user = insert(:user) -    other_user = insert(:user, %{nickname: "shp"}) - -    {:ok, activity} = CommonAPI.post(user, %{"status" => "Hey @shp!"}) -    {:ok, like, _object} = CommonAPI.favorite(activity.id, other_user) -    CommonAPI.delete(activity.id, user) - -    result = ActivityView.render("activity.json", activity: like) - -    expected = %{ -      "activity_type" => "like", -      "created_at" => like.data["published"] |> Utils.date_to_asctime(), -      "external_url" => like.data["id"], -      "id" => like.id, -      "in_reply_to_status_id" => nil, -      "is_local" => true, -      "is_post_verb" => false, -      "favorited_status" => nil, -      "statusnet_html" => "shp favorited a status.", -      "text" => "shp favorited a status.", -      "uri" => "tag:#{like.data["id"]}:objectType=Favourite", -      "user" => UserView.render("show.json", user: other_user) -    } - -    assert result == expected -  end - -  test "an announce activity" do -    user = insert(:user) -    other_user = insert(:user, %{nickname: "shp"}) - -    {:ok, activity} = CommonAPI.post(user, %{"status" => "Hey @shp!"}) -    {:ok, announce, object} = CommonAPI.repeat(activity.id, other_user) - -    convo_id = Utils.context_to_conversation_id(object.data["context"]) - -    activity = Activity.get_by_id(activity.id) - -    result = ActivityView.render("activity.json", activity: announce) - -    expected = %{ -      "activity_type" => "repeat", -      "created_at" => announce.data["published"] |> Utils.date_to_asctime(), -      "external_url" => announce.data["id"], -      "id" => announce.id, -      "is_local" => true, -      "is_post_verb" => false, -      "statusnet_html" => "shp repeated a status.", -      "text" => "shp repeated a status.", -      "uri" => "tag:#{announce.data["id"]}:objectType=note", -      "user" => UserView.render("show.json", user: other_user), -      "retweeted_status" => ActivityView.render("activity.json", activity: activity), -      "statusnet_conversation_id" => convo_id -    } - -    assert result == expected -  end - -  test "A follow activity" do -    user = insert(:user) -    other_user = insert(:user, %{nickname: "shp"}) - -    {:ok, follower} = User.follow(user, other_user) -    {:ok, follow} = ActivityPub.follow(follower, other_user) - -    result = ActivityView.render("activity.json", activity: follow) - -    expected = %{ -      "activity_type" => "follow", -      "attentions" => [], -      "created_at" => follow.data["published"] |> Utils.date_to_asctime(), -      "external_url" => follow.data["id"], -      "id" => follow.id, -      "in_reply_to_status_id" => nil, -      "is_local" => true, -      "is_post_verb" => false, -      "statusnet_html" => "#{user.nickname} started following shp", -      "text" => "#{user.nickname} started following shp", -      "user" => UserView.render("show.json", user: user) -    } - -    assert result == expected -  end - -  test "a delete activity" do -    user = insert(:user) - -    {:ok, activity} = CommonAPI.post(user, %{"status" => "Hey @shp!"}) -    {:ok, delete} = CommonAPI.delete(activity.id, user) - -    result = ActivityView.render("activity.json", activity: delete) - -    expected = %{ -      "activity_type" => "delete", -      "attentions" => [], -      "created_at" => delete.data["published"] |> Utils.date_to_asctime(), -      "external_url" => delete.data["id"], -      "id" => delete.id, -      "in_reply_to_status_id" => nil, -      "is_local" => true, -      "is_post_verb" => false, -      "statusnet_html" => "deleted notice {{tag", -      "text" => "deleted notice {{tag", -      "uri" => Object.normalize(delete).data["id"], -      "user" => UserView.render("show.json", user: user) -    } - -    assert result == expected -  end - -  test "a peertube video" do -    {:ok, object} = -      Pleroma.Object.Fetcher.fetch_object_from_id( -        "https://peertube.moe/videos/watch/df5f464b-be8d-46fb-ad81-2d4c2d1630e3" -      ) - -    %Activity{} = activity = Activity.get_create_by_object_ap_id(object.data["id"]) - -    result = ActivityView.render("activity.json", activity: activity) - -    assert length(result["attachments"]) == 1 -    assert result["summary"] == "Friday Night" -  end - -  test "special characters are not escaped in text field for status created" do -    text = "<3 is on the way" - -    {:ok, activity} = CommonAPI.post(insert(:user), %{"status" => text}) - -    result = ActivityView.render("activity.json", activity: activity) - -    assert result["text"] == text -  end -end diff --git a/test/web/twitter_api/views/notification_view_test.exs b/test/web/twitter_api/views/notification_view_test.exs deleted file mode 100644 index 6baeeaf63..000000000 --- a/test/web/twitter_api/views/notification_view_test.exs +++ /dev/null @@ -1,112 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/> -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Web.TwitterAPI.NotificationViewTest do -  use Pleroma.DataCase - -  alias Pleroma.Notification -  alias Pleroma.User -  alias Pleroma.Web.ActivityPub.ActivityPub -  alias Pleroma.Web.CommonAPI.Utils -  alias Pleroma.Web.TwitterAPI.ActivityView -  alias Pleroma.Web.TwitterAPI.NotificationView -  alias Pleroma.Web.TwitterAPI.TwitterAPI -  alias Pleroma.Web.TwitterAPI.UserView - -  import Pleroma.Factory - -  setup do -    user = insert(:user, bio: "<span>Here's some html</span>") -    [user: user] -  end - -  test "A follow notification" do -    note_activity = insert(:note_activity) -    user = User.get_cached_by_ap_id(note_activity.data["actor"]) -    follower = insert(:user) - -    {:ok, follower} = User.follow(follower, user) -    {:ok, activity} = ActivityPub.follow(follower, user) -    Cachex.put(:user_cache, "user_info:#{user.id}", User.user_info(Repo.get!(User, user.id))) -    [follow_notif] = Notification.for_user(user) - -    represented = %{ -      "created_at" => follow_notif.inserted_at |> Utils.format_naive_asctime(), -      "from_profile" => UserView.render("show.json", %{user: follower, for: user}), -      "id" => follow_notif.id, -      "is_seen" => 0, -      "notice" => ActivityView.render("activity.json", %{activity: activity, for: user}), -      "ntype" => "follow" -    } - -    assert represented == -             NotificationView.render("notification.json", %{notification: follow_notif, for: user}) -  end - -  test "A mention notification" do -    user = insert(:user) -    other_user = insert(:user) - -    {:ok, activity} = -      TwitterAPI.create_status(other_user, %{"status" => "Päivää, @#{user.nickname}"}) - -    [notification] = Notification.for_user(user) - -    represented = %{ -      "created_at" => notification.inserted_at |> Utils.format_naive_asctime(), -      "from_profile" => UserView.render("show.json", %{user: other_user, for: user}), -      "id" => notification.id, -      "is_seen" => 0, -      "notice" => ActivityView.render("activity.json", %{activity: activity, for: user}), -      "ntype" => "mention" -    } - -    assert represented == -             NotificationView.render("notification.json", %{notification: notification, for: user}) -  end - -  test "A retweet notification" do -    note_activity = insert(:note_activity) -    user = User.get_cached_by_ap_id(note_activity.data["actor"]) -    repeater = insert(:user) - -    {:ok, _activity} = TwitterAPI.repeat(repeater, note_activity.id) -    [notification] = Notification.for_user(user) - -    represented = %{ -      "created_at" => notification.inserted_at |> Utils.format_naive_asctime(), -      "from_profile" => UserView.render("show.json", %{user: repeater, for: user}), -      "id" => notification.id, -      "is_seen" => 0, -      "notice" => -        ActivityView.render("activity.json", %{activity: notification.activity, for: user}), -      "ntype" => "repeat" -    } - -    assert represented == -             NotificationView.render("notification.json", %{notification: notification, for: user}) -  end - -  test "A like notification" do -    note_activity = insert(:note_activity) -    user = User.get_cached_by_ap_id(note_activity.data["actor"]) -    liker = insert(:user) - -    {:ok, _activity} = TwitterAPI.fav(liker, note_activity.id) -    [notification] = Notification.for_user(user) - -    represented = %{ -      "created_at" => notification.inserted_at |> Utils.format_naive_asctime(), -      "from_profile" => UserView.render("show.json", %{user: liker, for: user}), -      "id" => notification.id, -      "is_seen" => 0, -      "notice" => -        ActivityView.render("activity.json", %{activity: notification.activity, for: user}), -      "ntype" => "like" -    } - -    assert represented == -             NotificationView.render("notification.json", %{notification: notification, for: user}) -  end -end diff --git a/test/web/twitter_api/views/user_view_test.exs b/test/web/twitter_api/views/user_view_test.exs deleted file mode 100644 index 70c5a0b7f..000000000 --- a/test/web/twitter_api/views/user_view_test.exs +++ /dev/null @@ -1,323 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/> -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Web.TwitterAPI.UserViewTest do -  use Pleroma.DataCase - -  alias Pleroma.User -  alias Pleroma.Web.CommonAPI.Utils -  alias Pleroma.Web.TwitterAPI.UserView - -  import Pleroma.Factory - -  setup do -    user = insert(:user, bio: "<span>Here's some html</span>") -    [user: user] -  end - -  test "A user with only a nickname", %{user: user} do -    user = %{user | name: nil, nickname: "scarlett@catgirl.science"} -    represented = UserView.render("show.json", %{user: user}) -    assert represented["name"] == user.nickname -    assert represented["name_html"] == user.nickname -  end - -  test "A user with an avatar object", %{user: user} do -    image = "image" -    user = %{user | avatar: %{"url" => [%{"href" => image}]}} -    represented = UserView.render("show.json", %{user: user}) -    assert represented["profile_image_url"] == image -  end - -  test "A user with emoji in username" do -    expected = -      "<img class=\"emoji\" alt=\"karjalanpiirakka\" title=\"karjalanpiirakka\" src=\"/file.png\" /> man" - -    user = -      insert(:user, %{ -        info: %{ -          source_data: %{ -            "tag" => [ -              %{ -                "type" => "Emoji", -                "icon" => %{"url" => "/file.png"}, -                "name" => ":karjalanpiirakka:" -              } -            ] -          } -        }, -        name: ":karjalanpiirakka: man" -      }) - -    represented = UserView.render("show.json", %{user: user}) -    assert represented["name_html"] == expected -  end - -  test "A user" do -    note_activity = insert(:note_activity) -    user = User.get_cached_by_ap_id(note_activity.data["actor"]) -    {:ok, user} = User.update_note_count(user) -    follower = insert(:user) -    second_follower = insert(:user) - -    User.follow(follower, user) -    User.follow(second_follower, user) -    User.follow(user, follower) -    {:ok, user} = User.update_follower_count(user) -    Cachex.put(:user_cache, "user_info:#{user.id}", User.user_info(Repo.get!(User, user.id))) - -    image = "http://localhost:4001/images/avi.png" -    banner = "http://localhost:4001/images/banner.png" - -    represented = %{ -      "id" => user.id, -      "name" => user.name, -      "screen_name" => user.nickname, -      "name_html" => user.name, -      "description" => HtmlSanitizeEx.strip_tags(user.bio |> String.replace("<br>", "\n")), -      "description_html" => HtmlSanitizeEx.basic_html(user.bio), -      "created_at" => user.inserted_at |> Utils.format_naive_asctime(), -      "favourites_count" => 0, -      "statuses_count" => 1, -      "friends_count" => 1, -      "followers_count" => 2, -      "profile_image_url" => image, -      "profile_image_url_https" => image, -      "profile_image_url_profile_size" => image, -      "profile_image_url_original" => image, -      "following" => false, -      "follows_you" => false, -      "statusnet_blocking" => false, -      "statusnet_profile_url" => user.ap_id, -      "cover_photo" => banner, -      "background_image" => nil, -      "is_local" => true, -      "locked" => false, -      "hide_follows" => false, -      "hide_followers" => false, -      "fields" => [], -      "pleroma" => %{ -        "confirmation_pending" => false, -        "tags" => [], -        "skip_thread_containment" => false -      }, -      "rights" => %{"admin" => false, "delete_others_notice" => false}, -      "role" => "member" -    } - -    assert represented == UserView.render("show.json", %{user: user}) -  end - -  test "User exposes settings for themselves and only for themselves", %{user: user} do -    as_user = UserView.render("show.json", %{user: user, for: user}) -    assert as_user["default_scope"] == user.info.default_scope -    assert as_user["no_rich_text"] == user.info.no_rich_text -    assert as_user["pleroma"]["notification_settings"] == user.info.notification_settings -    as_stranger = UserView.render("show.json", %{user: user}) -    refute as_stranger["default_scope"] -    refute as_stranger["no_rich_text"] -    refute as_stranger["pleroma"]["notification_settings"] -  end - -  test "A user for a given other follower", %{user: user} do -    follower = insert(:user, %{following: [User.ap_followers(user)]}) -    {:ok, user} = User.update_follower_count(user) -    image = "http://localhost:4001/images/avi.png" -    banner = "http://localhost:4001/images/banner.png" - -    represented = %{ -      "id" => user.id, -      "name" => user.name, -      "screen_name" => user.nickname, -      "name_html" => user.name, -      "description" => HtmlSanitizeEx.strip_tags(user.bio |> String.replace("<br>", "\n")), -      "description_html" => HtmlSanitizeEx.basic_html(user.bio), -      "created_at" => user.inserted_at |> Utils.format_naive_asctime(), -      "favourites_count" => 0, -      "statuses_count" => 0, -      "friends_count" => 0, -      "followers_count" => 1, -      "profile_image_url" => image, -      "profile_image_url_https" => image, -      "profile_image_url_profile_size" => image, -      "profile_image_url_original" => image, -      "following" => true, -      "follows_you" => false, -      "statusnet_blocking" => false, -      "statusnet_profile_url" => user.ap_id, -      "cover_photo" => banner, -      "background_image" => nil, -      "is_local" => true, -      "locked" => false, -      "hide_follows" => false, -      "hide_followers" => false, -      "fields" => [], -      "pleroma" => %{ -        "confirmation_pending" => false, -        "tags" => [], -        "skip_thread_containment" => false -      }, -      "rights" => %{"admin" => false, "delete_others_notice" => false}, -      "role" => "member" -    } - -    assert represented == UserView.render("show.json", %{user: user, for: follower}) -  end - -  test "A user that follows you", %{user: user} do -    follower = insert(:user) -    {:ok, follower} = User.follow(follower, user) -    {:ok, user} = User.update_follower_count(user) -    image = "http://localhost:4001/images/avi.png" -    banner = "http://localhost:4001/images/banner.png" - -    represented = %{ -      "id" => follower.id, -      "name" => follower.name, -      "screen_name" => follower.nickname, -      "name_html" => follower.name, -      "description" => HtmlSanitizeEx.strip_tags(follower.bio |> String.replace("<br>", "\n")), -      "description_html" => HtmlSanitizeEx.basic_html(follower.bio), -      "created_at" => follower.inserted_at |> Utils.format_naive_asctime(), -      "favourites_count" => 0, -      "statuses_count" => 0, -      "friends_count" => 1, -      "followers_count" => 0, -      "profile_image_url" => image, -      "profile_image_url_https" => image, -      "profile_image_url_profile_size" => image, -      "profile_image_url_original" => image, -      "following" => false, -      "follows_you" => true, -      "statusnet_blocking" => false, -      "statusnet_profile_url" => follower.ap_id, -      "cover_photo" => banner, -      "background_image" => nil, -      "is_local" => true, -      "locked" => false, -      "hide_follows" => false, -      "hide_followers" => false, -      "fields" => [], -      "pleroma" => %{ -        "confirmation_pending" => false, -        "tags" => [], -        "skip_thread_containment" => false -      }, -      "rights" => %{"admin" => false, "delete_others_notice" => false}, -      "role" => "member" -    } - -    assert represented == UserView.render("show.json", %{user: follower, for: user}) -  end - -  test "a user that is a moderator" do -    user = insert(:user, %{info: %{is_moderator: true}}) -    represented = UserView.render("show.json", %{user: user, for: user}) - -    assert represented["rights"]["delete_others_notice"] -    assert represented["role"] == "moderator" -  end - -  test "a user that is a admin" do -    user = insert(:user, %{info: %{is_admin: true}}) -    represented = UserView.render("show.json", %{user: user, for: user}) - -    assert represented["rights"]["admin"] -    assert represented["role"] == "admin" -  end - -  test "A moderator with hidden role for another user", %{user: user} do -    admin = insert(:user, %{info: %{is_moderator: true, show_role: false}}) -    represented = UserView.render("show.json", %{user: admin, for: user}) - -    assert represented["role"] == nil -  end - -  test "An admin with hidden role for another user", %{user: user} do -    admin = insert(:user, %{info: %{is_admin: true, show_role: false}}) -    represented = UserView.render("show.json", %{user: admin, for: user}) - -    assert represented["role"] == nil -  end - -  test "A regular user for the admin", %{user: user} do -    admin = insert(:user, %{info: %{is_admin: true}}) -    represented = UserView.render("show.json", %{user: user, for: admin}) - -    assert represented["pleroma"]["deactivated"] == false -  end - -  test "A blocked user for the blocker" do -    user = insert(:user) -    blocker = insert(:user) -    User.block(blocker, user) -    image = "http://localhost:4001/images/avi.png" -    banner = "http://localhost:4001/images/banner.png" - -    represented = %{ -      "id" => user.id, -      "name" => user.name, -      "screen_name" => user.nickname, -      "name_html" => user.name, -      "description" => HtmlSanitizeEx.strip_tags(user.bio |> String.replace("<br>", "\n")), -      "description_html" => HtmlSanitizeEx.basic_html(user.bio), -      "created_at" => user.inserted_at |> Utils.format_naive_asctime(), -      "favourites_count" => 0, -      "statuses_count" => 0, -      "friends_count" => 0, -      "followers_count" => 0, -      "profile_image_url" => image, -      "profile_image_url_https" => image, -      "profile_image_url_profile_size" => image, -      "profile_image_url_original" => image, -      "following" => false, -      "follows_you" => false, -      "statusnet_blocking" => true, -      "statusnet_profile_url" => user.ap_id, -      "cover_photo" => banner, -      "background_image" => nil, -      "is_local" => true, -      "locked" => false, -      "hide_follows" => false, -      "hide_followers" => false, -      "fields" => [], -      "pleroma" => %{ -        "confirmation_pending" => false, -        "tags" => [], -        "skip_thread_containment" => false -      }, -      "rights" => %{"admin" => false, "delete_others_notice" => false}, -      "role" => "member" -    } - -    blocker = User.get_cached_by_id(blocker.id) -    assert represented == UserView.render("show.json", %{user: user, for: blocker}) -  end - -  test "a user with mastodon fields" do -    fields = [ -      %{ -        "name" => "Pronouns", -        "value" => "she/her" -      }, -      %{ -        "name" => "Website", -        "value" => "https://example.org/" -      } -    ] - -    user = -      insert(:user, %{ -        info: %{ -          source_data: %{ -            "attachment" => -              Enum.map(fields, fn field -> Map.put(field, "type", "PropertyValue") end) -          } -        } -      }) - -    userview = UserView.render("show.json", %{user: user}) -    assert userview["fields"] == fields -  end -end diff --git a/test/web/uploader_controller_test.exs b/test/web/uploader_controller_test.exs index 70028df1c..7c7f9a6ea 100644 --- a/test/web/uploader_controller_test.exs +++ b/test/web/uploader_controller_test.exs @@ -1,5 +1,5 @@  # Pleroma: A lightweight social networking server -# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.Web.UploaderControllerTest do diff --git a/test/web/views/error_view_test.exs b/test/web/views/error_view_test.exs index 3857d585f..4e5398c83 100644 --- a/test/web/views/error_view_test.exs +++ b/test/web/views/error_view_test.exs @@ -1,5 +1,5 @@  # Pleroma: A lightweight social networking server -# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.Web.ErrorViewTest do diff --git a/test/web/web_finger/web_finger_controller_test.exs b/test/web/web_finger/web_finger_controller_test.exs index a14ed3126..49cd1460b 100644 --- a/test/web/web_finger/web_finger_controller_test.exs +++ b/test/web/web_finger/web_finger_controller_test.exs @@ -1,22 +1,34 @@  # Pleroma: A lightweight social networking server -# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.Web.WebFinger.WebFingerControllerTest do    use Pleroma.Web.ConnCase +  import ExUnit.CaptureLog    import Pleroma.Factory    import Tesla.Mock    setup do      mock(fn env -> apply(HttpRequestMock, :request, [env]) end) +    :ok +  end -    config_path = [:instance, :federating] -    initial_setting = Pleroma.Config.get(config_path) +  clear_config_all([:instance, :federating]) do +    Pleroma.Config.put([:instance, :federating], true) +  end -    Pleroma.Config.put(config_path, true) -    on_exit(fn -> Pleroma.Config.put(config_path, initial_setting) end) -    :ok +  test "GET host-meta" do +    response = +      build_conn() +      |> get("/.well-known/host-meta") + +    assert response.status == 200 + +    assert response.resp_body == +             ~s(<?xml version="1.0" encoding="UTF-8"?><XRD xmlns="http://docs.oasis-open.org/ns/xri/xrd-1.0"><Link rel="lrdd" template="#{ +               Pleroma.Web.base_url() +             }/.well-known/webfinger?resource={uri}" type="application/xrd+xml" /></XRD>)    end    test "Webfinger JRD" do @@ -30,6 +42,16 @@ defmodule Pleroma.Web.WebFinger.WebFingerControllerTest do      assert json_response(response, 200)["subject"] == "acct:#{user.nickname}@localhost"    end +  test "it returns 404 when user isn't found (JSON)" do +    result = +      build_conn() +      |> put_req_header("accept", "application/jrd+json") +      |> get("/.well-known/webfinger?resource=acct:jimm@localhost") +      |> json_response(404) + +    assert result == "Couldn't find user" +  end +    test "Webfinger XML" do      user = insert(:user) @@ -41,6 +63,28 @@ defmodule Pleroma.Web.WebFinger.WebFingerControllerTest do      assert response(response, 200)    end +  test "it returns 404 when user isn't found (XML)" do +    result = +      build_conn() +      |> put_req_header("accept", "application/xrd+xml") +      |> get("/.well-known/webfinger?resource=acct:jimm@localhost") +      |> response(404) + +    assert result == "Couldn't find user" +  end + +  test "Sends a 404 when invalid format" do +    user = insert(:user) + +    assert capture_log(fn -> +             assert_raise Phoenix.NotAcceptableError, fn -> +               build_conn() +               |> put_req_header("accept", "text/html") +               |> get("/.well-known/webfinger?resource=acct:#{user.nickname}@localhost") +             end +           end) =~ "no supported media type in accept header" +  end +    test "Sends a 400 when resource param is missing" do      response =        build_conn() diff --git a/test/web/web_finger/web_finger_test.exs b/test/web/web_finger/web_finger_test.exs index 0578b4b8e..5aa8c73cf 100644 --- a/test/web/web_finger/web_finger_test.exs +++ b/test/web/web_finger/web_finger_test.exs @@ -1,5 +1,5 @@  # Pleroma: A lightweight social networking server -# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.Web.WebFingerTest do @@ -40,17 +40,9 @@ defmodule Pleroma.Web.WebFingerTest do    end    describe "fingering" do -    test "returns the info for an OStatus user" do -      user = "shp@social.heldscal.la" - -      {:ok, data} = WebFinger.finger(user) - -      assert data["magic_key"] == -               "RSA.wQ3i9UA0qmAxZ0WTIp4a-waZn_17Ez1pEEmqmqoooRsG1_BvpmOvLN0G2tEcWWxl2KOtdQMCiPptmQObeZeuj48mdsDZ4ArQinexY2hCCTcbV8Xpswpkb8K05RcKipdg07pnI7tAgQ0VWSZDImncL6YUGlG5YN8b5TjGOwk2VG8=.AQAB" - -      assert data["topic"] == "https://social.heldscal.la/api/statuses/user_timeline/29191.atom" -      assert data["subject"] == "acct:shp@social.heldscal.la" -      assert data["salmon"] == "https://social.heldscal.la/main/salmon/user/29191" +    test "returns error when fails parse xml or json" do +      user = "invalid_content@social.heldscal.la" +      assert {:error, %Jason.DecodeError{}} = WebFinger.finger(user)      end      test "returns the ActivityPub actor URI for an ActivityPub user" do @@ -67,18 +59,18 @@ defmodule Pleroma.Web.WebFingerTest do        assert data["ap_id"] == "https://gerzilla.de/channel/kaniini"      end -    test "returns the correctly for json ostatus users" do -      user = "winterdienst@gnusocial.de" +    test "it work for AP-only user" do +      user = "kpherox@mstdn.jp"        {:ok, data} = WebFinger.finger(user) -      assert data["magic_key"] == -               "RSA.qfYaxztz7ZELrE4v5WpJrPM99SKI3iv9Y3Tw6nfLGk-4CRljNYqV8IYX2FXjeucC_DKhPNnlF6fXyASpcSmA_qupX9WC66eVhFhZ5OuyBOeLvJ1C4x7Hi7Di8MNBxY3VdQuQR0tTaS_YAZCwASKp7H6XEid3EJpGt0EQZoNzRd8=.AQAB" +      assert data["magic_key"] == nil +      assert data["salmon"] == nil -      assert data["topic"] == "https://gnusocial.de/api/statuses/user_timeline/249296.atom" -      assert data["subject"] == "acct:winterdienst@gnusocial.de" -      assert data["salmon"] == "https://gnusocial.de/main/salmon/user/249296" -      assert data["subscribe_address"] == "https://gnusocial.de/main/ostatussub?profile={uri}" +      assert data["topic"] == "https://mstdn.jp/users/kPherox.atom" +      assert data["subject"] == "acct:kPherox@mstdn.jp" +      assert data["ap_id"] == "https://mstdn.jp/users/kPherox" +      assert data["subscribe_address"] == "https://mstdn.jp/authorize_interaction?acct={uri}"      end      test "it works for friendica" do diff --git a/test/web/websub/websub_controller_test.exs b/test/web/websub/websub_controller_test.exs deleted file mode 100644 index aa7262beb..000000000 --- a/test/web/websub/websub_controller_test.exs +++ /dev/null @@ -1,92 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/> -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Web.Websub.WebsubControllerTest do -  use Pleroma.Web.ConnCase -  import Pleroma.Factory -  alias Pleroma.Repo -  alias Pleroma.Web.Websub -  alias Pleroma.Web.Websub.WebsubClientSubscription - -  setup_all do -    config_path = [:instance, :federating] -    initial_setting = Pleroma.Config.get(config_path) - -    Pleroma.Config.put(config_path, true) -    on_exit(fn -> Pleroma.Config.put(config_path, initial_setting) end) - -    :ok -  end - -  test "websub subscription request", %{conn: conn} do -    user = insert(:user) - -    path = Pleroma.Web.OStatus.pubsub_path(user) - -    data = %{ -      "hub.callback": "http://example.org/sub", -      "hub.mode": "subscribe", -      "hub.topic": Pleroma.Web.OStatus.feed_path(user), -      "hub.secret": "a random secret", -      "hub.lease_seconds": "100" -    } - -    conn = -      conn -      |> post(path, data) - -    assert response(conn, 202) == "Accepted" -  end - -  test "websub subscription confirmation", %{conn: conn} do -    websub = insert(:websub_client_subscription) - -    params = %{ -      "hub.mode" => "subscribe", -      "hub.topic" => websub.topic, -      "hub.challenge" => "some challenge", -      "hub.lease_seconds" => "100" -    } - -    conn = -      conn -      |> get("/push/subscriptions/#{websub.id}", params) - -    websub = Repo.get(WebsubClientSubscription, websub.id) - -    assert response(conn, 200) == "some challenge" -    assert websub.state == "accepted" -    assert_in_delta NaiveDateTime.diff(websub.valid_until, NaiveDateTime.utc_now()), 100, 5 -  end - -  describe "websub_incoming" do -    test "accepts incoming feed updates", %{conn: conn} do -      websub = insert(:websub_client_subscription) -      doc = "some stuff" -      signature = Websub.sign(websub.secret, doc) - -      conn = -        conn -        |> put_req_header("x-hub-signature", "sha1=" <> signature) -        |> put_req_header("content-type", "application/atom+xml") -        |> post("/push/subscriptions/#{websub.id}", doc) - -      assert response(conn, 200) == "OK" -    end - -    test "rejects incoming feed updates with the wrong signature", %{conn: conn} do -      websub = insert(:websub_client_subscription) -      doc = "some stuff" -      signature = Websub.sign("wrong secret", doc) - -      conn = -        conn -        |> put_req_header("x-hub-signature", "sha1=" <> signature) -        |> put_req_header("content-type", "application/atom+xml") -        |> post("/push/subscriptions/#{websub.id}", doc) - -      assert response(conn, 500) == "Error" -    end -  end -end diff --git a/test/web/websub/websub_test.exs b/test/web/websub/websub_test.exs deleted file mode 100644 index 74386d7db..000000000 --- a/test/web/websub/websub_test.exs +++ /dev/null @@ -1,232 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/> -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Web.WebsubTest do -  use Pleroma.DataCase - -  alias Pleroma.Web.Router.Helpers -  alias Pleroma.Web.Websub -  alias Pleroma.Web.Websub.WebsubClientSubscription -  alias Pleroma.Web.Websub.WebsubServerSubscription - -  import Pleroma.Factory -  import Tesla.Mock - -  setup do -    mock(fn env -> apply(HttpRequestMock, :request, [env]) end) -    :ok -  end - -  test "a verification of a request that is accepted" do -    sub = insert(:websub_subscription) -    topic = sub.topic - -    getter = fn _path, _headers, options -> -      %{ -        "hub.challenge": challenge, -        "hub.lease_seconds": seconds, -        "hub.topic": ^topic, -        "hub.mode": "subscribe" -      } = Keyword.get(options, :params) - -      assert String.to_integer(seconds) > 0 - -      {:ok, -       %Tesla.Env{ -         status: 200, -         body: challenge -       }} -    end - -    {:ok, sub} = Websub.verify(sub, getter) -    assert sub.state == "active" -  end - -  test "a verification of a request that doesn't return 200" do -    sub = insert(:websub_subscription) - -    getter = fn _path, _headers, _options -> -      {:ok, -       %Tesla.Env{ -         status: 500, -         body: "" -       }} -    end - -    {:error, sub} = Websub.verify(sub, getter) -    # Keep the current state. -    assert sub.state == "requested" -  end - -  test "an incoming subscription request" do -    user = insert(:user) - -    data = %{ -      "hub.callback" => "http://example.org/sub", -      "hub.mode" => "subscribe", -      "hub.topic" => Pleroma.Web.OStatus.feed_path(user), -      "hub.secret" => "a random secret", -      "hub.lease_seconds" => "100" -    } - -    {:ok, subscription} = Websub.incoming_subscription_request(user, data) -    assert subscription.topic == Pleroma.Web.OStatus.feed_path(user) -    assert subscription.state == "requested" -    assert subscription.secret == "a random secret" -    assert subscription.callback == "http://example.org/sub" -  end - -  test "an incoming subscription request for an existing subscription" do -    user = insert(:user) - -    sub = -      insert(:websub_subscription, state: "accepted", topic: Pleroma.Web.OStatus.feed_path(user)) - -    data = %{ -      "hub.callback" => sub.callback, -      "hub.mode" => "subscribe", -      "hub.topic" => Pleroma.Web.OStatus.feed_path(user), -      "hub.secret" => "a random secret", -      "hub.lease_seconds" => "100" -    } - -    {:ok, subscription} = Websub.incoming_subscription_request(user, data) -    assert subscription.topic == Pleroma.Web.OStatus.feed_path(user) -    assert subscription.state == sub.state -    assert subscription.secret == "a random secret" -    assert subscription.callback == sub.callback -    assert length(Repo.all(WebsubServerSubscription)) == 1 -    assert subscription.id == sub.id -  end - -  def accepting_verifier(subscription) do -    {:ok, %{subscription | state: "accepted"}} -  end - -  test "initiate a subscription for a given user and topic" do -    subscriber = insert(:user) -    user = insert(:user, %{info: %Pleroma.User.Info{topic: "some_topic", hub: "some_hub"}}) - -    {:ok, websub} = Websub.subscribe(subscriber, user, &accepting_verifier/1) -    assert websub.subscribers == [subscriber.ap_id] -    assert websub.topic == "some_topic" -    assert websub.hub == "some_hub" -    assert is_binary(websub.secret) -    assert websub.user == user -    assert websub.state == "accepted" -  end - -  test "discovers the hub and canonical url" do -    topic = "https://mastodon.social/users/lambadalambda.atom" - -    {:ok, discovered} = Websub.gather_feed_data(topic) - -    expected = %{ -      "hub" => "https://mastodon.social/api/push", -      "uri" => "https://mastodon.social/users/lambadalambda", -      "nickname" => "lambadalambda", -      "name" => "Critical Value", -      "host" => "mastodon.social", -      "bio" => "a cool dude.", -      "avatar" => %{ -        "type" => "Image", -        "url" => [ -          %{ -            "href" => -              "https://files.mastodon.social/accounts/avatars/000/000/264/original/1429214160519.gif?1492379244", -            "mediaType" => "image/gif", -            "type" => "Link" -          } -        ] -      } -    } - -    assert expected == discovered -  end - -  test "calls the hub, requests topic" do -    hub = "https://social.heldscal.la/main/push/hub" -    topic = "https://social.heldscal.la/api/statuses/user_timeline/23211.atom" -    websub = insert(:websub_client_subscription, %{hub: hub, topic: topic}) - -    poster = fn ^hub, {:form, data}, _headers -> -      assert Keyword.get(data, :"hub.mode") == "subscribe" - -      assert Keyword.get(data, :"hub.callback") == -               Helpers.websub_url( -                 Pleroma.Web.Endpoint, -                 :websub_subscription_confirmation, -                 websub.id -               ) - -      {:ok, %{status: 202}} -    end - -    task = Task.async(fn -> Websub.request_subscription(websub, poster) end) - -    change = Ecto.Changeset.change(websub, %{state: "accepted"}) -    {:ok, _} = Repo.update(change) - -    {:ok, websub} = Task.await(task) - -    assert websub.state == "accepted" -  end - -  test "rejects the subscription if it can't be accepted" do -    hub = "https://social.heldscal.la/main/push/hub" -    topic = "https://social.heldscal.la/api/statuses/user_timeline/23211.atom" -    websub = insert(:websub_client_subscription, %{hub: hub, topic: topic}) - -    poster = fn ^hub, {:form, _data}, _headers -> -      {:ok, %{status: 202}} -    end - -    {:error, websub} = Websub.request_subscription(websub, poster, 1000) -    assert websub.state == "rejected" - -    websub = insert(:websub_client_subscription, %{hub: hub, topic: topic}) - -    poster = fn ^hub, {:form, _data}, _headers -> -      {:ok, %{status: 400}} -    end - -    {:error, websub} = Websub.request_subscription(websub, poster, 1000) -    assert websub.state == "rejected" -  end - -  test "sign a text" do -    signed = Websub.sign("secret", "text") -    assert signed == "B8392C23690CCF871F37EC270BE1582DEC57A503" |> String.downcase() - -    _signed = Websub.sign("secret", [["て"], ['す']]) -  end - -  describe "renewing subscriptions" do -    test "it renews subscriptions that have less than a day of time left" do -      day = 60 * 60 * 24 -      now = NaiveDateTime.utc_now() - -      still_good = -        insert(:websub_client_subscription, %{ -          valid_until: NaiveDateTime.add(now, 2 * day), -          topic: "http://example.org/still_good", -          hub: "http://example.org/still_good", -          state: "accepted" -        }) - -      needs_refresh = -        insert(:websub_client_subscription, %{ -          valid_until: NaiveDateTime.add(now, day - 100), -          topic: "http://example.org/needs_refresh", -          hub: "http://example.org/needs_refresh", -          state: "accepted" -        }) - -      _refresh = Websub.refresh_subscriptions() - -      assert still_good == Repo.get(WebsubClientSubscription, still_good.id) -      refute needs_refresh == Repo.get(WebsubClientSubscription, needs_refresh.id) -    end -  end -end  | 
