Extract poll actions from `MastodonAPIController` to `PollController` See merge request pleroma/pleroma!1755object-id-column
@@ -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 |
@@ -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 | |||
@@ -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 |
@@ -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 | |||
@@ -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 |
@@ -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 | |||
@@ -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) | |||
@@ -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 |
@@ -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) | |||
@@ -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 |
@@ -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) | |||