diff options
| -rw-r--r-- | lib/pleroma/web/controller_helper.ex | 12 | ||||
| -rw-r--r-- | lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex | 64 | ||||
| -rw-r--r-- | lib/pleroma/web/mastodon_api/controllers/poll_controller.ex | 53 | ||||
| -rw-r--r-- | lib/pleroma/web/mastodon_api/controllers/status_controller.ex | 2 | ||||
| -rw-r--r-- | lib/pleroma/web/mastodon_api/views/poll_view.ex | 74 | ||||
| -rw-r--r-- | lib/pleroma/web/mastodon_api/views/status_view.ex | 72 | ||||
| -rw-r--r-- | lib/pleroma/web/router.ex | 4 | ||||
| -rw-r--r-- | test/web/mastodon_api/controllers/poll_controller_test.exs | 184 | ||||
| -rw-r--r-- | test/web/mastodon_api/mastodon_api_controller_test.exs | 172 | ||||
| -rw-r--r-- | test/web/mastodon_api/views/poll_view_test.exs | 126 | ||||
| -rw-r--r-- | test/web/mastodon_api/views/status_view_test.exs | 110 | 
11 files changed, 454 insertions, 419 deletions
diff --git a/lib/pleroma/web/controller_helper.ex b/lib/pleroma/web/controller_helper.ex index 83b884ba9..9a4e322c9 100644 --- a/lib/pleroma/web/controller_helper.ex +++ b/lib/pleroma/web/controller_helper.ex @@ -75,4 +75,16 @@ defmodule Pleroma.Web.ControllerHelper do        nil -> Pleroma.Web.MastodonAPI.FallbackController.call(conn, {:error, :not_found}) |> halt()      end    end + +  def try_render(conn, target, params) +      when is_binary(target) do +    case render(conn, target, params) do +      nil -> render_error(conn, :not_implemented, "Can't display this activity") +      res -> res +    end +  end + +  def try_render(conn, _, _) do +    render_error(conn, :not_implemented, "Can't display this activity") +  end  end diff --git a/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex b/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex index 1484a0174..912dd181f 100644 --- a/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex @@ -7,7 +7,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do    import Pleroma.Web.ControllerHelper, only: [add_link_headers: 2] -  alias Pleroma.Activity    alias Pleroma.Bookmark    alias Pleroma.Config    alias Pleroma.HTTP @@ -19,7 +18,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do    alias Pleroma.User    alias Pleroma.Web    alias Pleroma.Web.ActivityPub.ActivityPub -  alias Pleroma.Web.ActivityPub.Visibility    alias Pleroma.Web.CommonAPI    alias Pleroma.Web.MastodonAPI.AccountView    alias Pleroma.Web.MastodonAPI.AppView @@ -117,56 +115,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do      json(conn, mastodon_emoji)    end -  def get_poll(%{assigns: %{user: user}} = conn, %{"id" => id}) do -    with %Object{} = object <- Object.get_by_id_and_maybe_refetch(id, interval: 60), -         %Activity{} = activity <- Activity.get_create_by_object_ap_id(object.data["id"]), -         true <- Visibility.visible_for_user?(activity, user) do -      conn -      |> put_view(StatusView) -      |> try_render("poll.json", %{object: object, for: user}) -    else -      error when is_nil(error) or error == false -> -        render_error(conn, :not_found, "Record not found") -    end -  end - -  defp get_cached_vote_or_vote(user, object, choices) do -    idempotency_key = "polls:#{user.id}:#{object.data["id"]}" - -    {_, res} = -      Cachex.fetch(:idempotency_cache, idempotency_key, fn _ -> -        case CommonAPI.vote(user, object, choices) do -          {:error, _message} = res -> {:ignore, res} -          res -> {:commit, res} -        end -      end) - -    res -  end - -  def poll_vote(%{assigns: %{user: user}} = conn, %{"id" => id, "choices" => choices}) do -    with %Object{} = object <- Object.get_by_id(id), -         true <- object.data["type"] == "Question", -         %Activity{} = activity <- Activity.get_create_by_object_ap_id(object.data["id"]), -         true <- Visibility.visible_for_user?(activity, user), -         {:ok, _activities, object} <- get_cached_vote_or_vote(user, object, choices) do -      conn -      |> put_view(StatusView) -      |> try_render("poll.json", %{object: object, for: user}) -    else -      nil -> -        render_error(conn, :not_found, "Record not found") - -      false -> -        render_error(conn, :not_found, "Record not found") - -      {:error, message} -> -        conn -        |> put_status(:unprocessable_entity) -        |> json(%{error: message}) -    end -  end -    def update_media(          %{assigns: %{user: user}} = conn,          %{"id" => id, "description" => description} = _ @@ -511,18 +459,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do      end    end -  def try_render(conn, target, params) -      when is_binary(target) do -    case render(conn, target, params) do -      nil -> render_error(conn, :not_implemented, "Can't display this activity") -      res -> res -    end -  end - -  def try_render(conn, _, _) do -    render_error(conn, :not_implemented, "Can't display this activity") -  end -    defp present?(nil), do: false    defp present?(false), do: false    defp present?(_), do: true diff --git a/lib/pleroma/web/mastodon_api/controllers/poll_controller.ex b/lib/pleroma/web/mastodon_api/controllers/poll_controller.ex new file mode 100644 index 000000000..fbf7f8673 --- /dev/null +++ b/lib/pleroma/web/mastodon_api/controllers/poll_controller.ex @@ -0,0 +1,53 @@ +# 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.PollController do +  use Pleroma.Web, :controller + +  import Pleroma.Web.ControllerHelper, only: [try_render: 3, json_response: 3] + +  alias Pleroma.Activity +  alias Pleroma.Object +  alias Pleroma.Web.ActivityPub.Visibility +  alias Pleroma.Web.CommonAPI + +  action_fallback(Pleroma.Web.MastodonAPI.FallbackController) + +  @doc "GET /api/v1/polls/:id" +  def show(%{assigns: %{user: user}} = conn, %{"id" => id}) do +    with %Object{} = object <- Object.get_by_id_and_maybe_refetch(id, interval: 60), +         %Activity{} = activity <- Activity.get_create_by_object_ap_id(object.data["id"]), +         true <- Visibility.visible_for_user?(activity, user) do +      try_render(conn, "show.json", %{object: object, for: user}) +    else +      error when is_nil(error) or error == false -> +        render_error(conn, :not_found, "Record not found") +    end +  end + +  @doc "POST /api/v1/polls/:id/votes" +  def vote(%{assigns: %{user: user}} = conn, %{"id" => id, "choices" => choices}) do +    with %Object{data: %{"type" => "Question"}} = object <- Object.get_by_id(id), +         %Activity{} = activity <- Activity.get_create_by_object_ap_id(object.data["id"]), +         true <- Visibility.visible_for_user?(activity, user), +         {:ok, _activities, object} <- get_cached_vote_or_vote(user, object, choices) do +      try_render(conn, "show.json", %{object: object, for: user}) +    else +      nil -> render_error(conn, :not_found, "Record not found") +      false -> render_error(conn, :not_found, "Record not found") +      {:error, message} -> json_response(conn, :unprocessable_entity, %{error: message}) +    end +  end + +  defp get_cached_vote_or_vote(user, object, choices) do +    idempotency_key = "polls:#{user.id}:#{object.data["id"]}" + +    Cachex.fetch!(:idempotency_cache, idempotency_key, fn -> +      case CommonAPI.vote(user, object, choices) do +        {:error, _message} = res -> {:ignore, res} +        res -> {:commit, res} +      end +    end) +  end +end diff --git a/lib/pleroma/web/mastodon_api/controllers/status_controller.ex b/lib/pleroma/web/mastodon_api/controllers/status_controller.ex index 3c6987a5f..fb6fd7676 100644 --- a/lib/pleroma/web/mastodon_api/controllers/status_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/status_controller.ex @@ -5,7 +5,7 @@  defmodule Pleroma.Web.MastodonAPI.StatusController do    use Pleroma.Web, :controller -  import Pleroma.Web.MastodonAPI.MastodonAPIController, only: [try_render: 3] +  import Pleroma.Web.ControllerHelper, only: [try_render: 3]    require Ecto.Query diff --git a/lib/pleroma/web/mastodon_api/views/poll_view.ex b/lib/pleroma/web/mastodon_api/views/poll_view.ex new file mode 100644 index 000000000..753039da3 --- /dev/null +++ b/lib/pleroma/web/mastodon_api/views/poll_view.ex @@ -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.PollView do +  use Pleroma.Web, :view + +  alias Pleroma.HTML +  alias Pleroma.Web.CommonAPI.Utils + +  def render("show.json", %{object: object, multiple: multiple, options: options} = params) do +    {end_time, expired} = end_time_and_expired(object) +    {options, votes_count} = options_and_votes_count(options) + +    %{ +      # Mastodon uses separate ids for polls, but an object can't have +      # more than one poll embedded so object id is fine +      id: to_string(object.id), +      expires_at: end_time, +      expired: expired, +      multiple: multiple, +      votes_count: votes_count, +      options: options, +      voted: voted?(params), +      emojis: Pleroma.Web.MastodonAPI.StatusView.build_emojis(object.data["emoji"]) +    } +  end + +  def render("show.json", %{object: object} = params) do +    case object.data do +      %{"anyOf" => options} when is_list(options) -> +        render(__MODULE__, "show.json", Map.merge(params, %{multiple: true, options: options})) + +      %{"oneOf" => options} when is_list(options) -> +        render(__MODULE__, "show.json", Map.merge(params, %{multiple: false, options: options})) + +      _ -> +        nil +    end +  end + +  defp end_time_and_expired(object) do +    case object.data["closed"] || object.data["endTime"] do +      end_time when is_binary(end_time) -> +        end_time = NaiveDateTime.from_iso8601!(end_time) +        expired = NaiveDateTime.compare(end_time, NaiveDateTime.utc_now()) == :lt + +        {Utils.to_masto_date(end_time), expired} + +      _ -> +        {nil, false} +    end +  end + +  defp options_and_votes_count(options) do +    Enum.map_reduce(options, 0, fn %{"name" => name} = option, count -> +      current_count = option["replies"]["totalItems"] || 0 + +      {%{ +         title: HTML.strip_tags(name), +         votes_count: current_count +       }, current_count + count} +    end) +  end + +  defp voted?(%{object: object} = opts) do +    if opts[:for] do +      existing_votes = Pleroma.Web.ActivityPub.Utils.get_existing_votes(opts[:for].ap_id, object) +      existing_votes != [] or opts[:for].ap_id == object.data["actor"] +    else +      false +    end +  end +end diff --git a/lib/pleroma/web/mastodon_api/views/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex index bc527ad1b..3262427ec 100644 --- a/lib/pleroma/web/mastodon_api/views/status_view.ex +++ b/lib/pleroma/web/mastodon_api/views/status_view.ex @@ -18,6 +18,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do    alias Pleroma.Web.CommonAPI    alias Pleroma.Web.CommonAPI.Utils    alias Pleroma.Web.MastodonAPI.AccountView +  alias Pleroma.Web.MastodonAPI.PollView    alias Pleroma.Web.MastodonAPI.StatusView    alias Pleroma.Web.MediaProxy @@ -277,7 +278,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do        spoiler_text: summary_html,        visibility: get_visibility(object),        media_attachments: attachments, -      poll: render("poll.json", %{object: object, for: opts[:for]}), +      poll: render(PollView, "show.json", object: object, for: opts[:for]),        mentions: mentions,        tags: build_tags(tags),        application: %{ @@ -389,75 +390,6 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do      safe_render_many(opts.activities, StatusView, "listen.json", opts)    end -  def render("poll.json", %{object: object} = opts) do -    {multiple, options} = -      case object.data do -        %{"anyOf" => options} when is_list(options) -> {true, options} -        %{"oneOf" => options} when is_list(options) -> {false, options} -        _ -> {nil, nil} -      end - -    if options do -      {end_time, expired} = -        case object.data["closed"] || object.data["endTime"] do -          end_time when is_binary(end_time) -> -            end_time = -              (object.data["closed"] || object.data["endTime"]) -              |> NaiveDateTime.from_iso8601!() - -            expired = -              end_time -              |> NaiveDateTime.compare(NaiveDateTime.utc_now()) -              |> case do -                :lt -> true -                _ -> false -              end - -            end_time = Utils.to_masto_date(end_time) - -            {end_time, expired} - -          _ -> -            {nil, false} -        end - -      voted = -        if opts[:for] do -          existing_votes = -            Pleroma.Web.ActivityPub.Utils.get_existing_votes(opts[:for].ap_id, object) - -          existing_votes != [] or opts[:for].ap_id == object.data["actor"] -        else -          false -        end - -      {options, votes_count} = -        Enum.map_reduce(options, 0, fn %{"name" => name} = option, count -> -          current_count = option["replies"]["totalItems"] || 0 - -          {%{ -             title: HTML.strip_tags(name), -             votes_count: current_count -           }, current_count + count} -        end) - -      %{ -        # Mastodon uses separate ids for polls, but an object can't have -        # more than one poll embedded so object id is fine -        id: to_string(object.id), -        expires_at: end_time, -        expired: expired, -        multiple: multiple, -        votes_count: votes_count, -        options: options, -        voted: voted, -        emojis: build_emojis(object.data["emoji"]) -      } -    else -      nil -    end -  end -    def render("context.json", %{activity: activity, activities: activities, user: user}) do      %{ancestors: ancestors, descendants: descendants} =        activities diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index eab55a27c..7af44c6be 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -403,7 +403,7 @@ defmodule Pleroma.Web.Router do        put("/scheduled_statuses/:id", ScheduledActivityController, :update)        delete("/scheduled_statuses/:id", ScheduledActivityController, :delete) -      post("/polls/:id/votes", MastodonAPIController, :poll_vote) +      post("/polls/:id/votes", PollController, :vote)        post("/media", MastodonAPIController, :upload)        put("/media/:id", MastodonAPIController, :update_media) @@ -488,7 +488,7 @@ defmodule Pleroma.Web.Router do        get("/statuses/:id", StatusController, :show)        get("/statuses/:id/context", StatusController, :context) -      get("/polls/:id", MastodonAPIController, :get_poll) +      get("/polls/:id", PollController, :show)        get("/accounts/:id/statuses", AccountController, :statuses)        get("/accounts/:id/followers", AccountController, :followers) 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..40cf3e879 --- /dev/null +++ b/test/web/mastodon_api/controllers/poll_controller_test.exs @@ -0,0 +1,184 @@ +# 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 +    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 +end diff --git a/test/web/mastodon_api/mastodon_api_controller_test.exs b/test/web/mastodon_api/mastodon_api_controller_test.exs index feeaf079b..2ec46bc90 100644 --- a/test/web/mastodon_api/mastodon_api_controller_test.exs +++ b/test/web/mastodon_api/mastodon_api_controller_test.exs @@ -417,178 +417,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do      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 "POST /auth/password, with valid parameters" do      setup %{conn: conn} do        user = insert(:user) 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/views/status_view_test.exs b/test/web/mastodon_api/views/status_view_test.exs index 8df23d0a8..1d5a6e956 100644 --- a/test/web/mastodon_api/views/status_view_test.exs +++ b/test/web/mastodon_api/views/status_view_test.exs @@ -451,116 +451,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 - -    test "does not crash on polls with no end date" do -      object = Object.normalize("https://skippers-bin.com/notes/7x9tmrp97i") -      result = StatusView.render("poll.json", %{object: object}) - -      assert result[:expires_at] == nil -      assert result[:expired] == false -    end -  end -    test "embeds a relationship in the account" do      user = insert(:user)      other_user = insert(:user)  | 
