Merge branch 'openapi/polls' into 'develop'

Add OpenAPI spec for PollController

See merge request pleroma/pleroma!2476
This commit is contained in:
lain 2020-05-07 09:19:45 +00:00
commit 4c92dfb73e
4 changed files with 162 additions and 22 deletions

View file

@ -0,0 +1,76 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ApiSpec.PollOperation do
alias OpenApiSpex.Operation
alias OpenApiSpex.Schema
alias Pleroma.Web.ApiSpec.Schemas.ApiError
alias Pleroma.Web.ApiSpec.Schemas.FlakeID
alias Pleroma.Web.ApiSpec.Schemas.Poll
import Pleroma.Web.ApiSpec.Helpers
def open_api_operation(action) do
operation = String.to_existing_atom("#{action}_operation")
apply(__MODULE__, operation, [])
end
def show_operation do
%Operation{
tags: ["Polls"],
summary: "View a poll",
security: [%{"oAuth" => ["read:statuses"]}],
parameters: [id_param()],
operationId: "PollController.show",
responses: %{
200 => Operation.response("Poll", "application/json", Poll),
404 => Operation.response("Error", "application/json", ApiError)
}
}
end
def vote_operation do
%Operation{
tags: ["Polls"],
summary: "Vote on a poll",
parameters: [id_param()],
operationId: "PollController.vote",
requestBody: vote_request(),
security: [%{"oAuth" => ["write:statuses"]}],
responses: %{
200 => Operation.response("Poll", "application/json", Poll),
422 => Operation.response("Error", "application/json", ApiError),
404 => Operation.response("Error", "application/json", ApiError)
}
}
end
defp id_param do
Operation.parameter(:id, :path, FlakeID, "Poll ID",
example: "123",
required: true
)
end
defp vote_request do
request_body(
"Parameters",
%Schema{
type: :object,
properties: %{
choices: %Schema{
type: :array,
items: %Schema{type: :integer},
description: "Array of own votes containing index for each option (starting from 0)"
}
},
required: [:choices]
},
required: true,
example: %{
"choices" => [0, 1, 2]
}
)
end
end

View file

@ -11,26 +11,72 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Poll do
OpenApiSpex.schema(%{ OpenApiSpex.schema(%{
title: "Poll", title: "Poll",
description: "Response schema for account custom fields", description: "Represents a poll attached to a status",
type: :object, type: :object,
properties: %{ properties: %{
id: FlakeID, id: FlakeID,
expires_at: %Schema{type: :string, format: "date-time"}, expires_at: %Schema{
expired: %Schema{type: :boolean}, type: :string,
multiple: %Schema{type: :boolean}, format: :"date-time",
votes_count: %Schema{type: :integer}, nullable: true,
voted: %Schema{type: :boolean}, description: "When the poll ends"
emojis: %Schema{type: :array, items: Emoji}, },
expired: %Schema{type: :boolean, description: "Is the poll currently expired?"},
multiple: %Schema{
type: :boolean,
description: "Does the poll allow multiple-choice answers?"
},
votes_count: %Schema{
type: :integer,
nullable: true,
description: "How many votes have been received. Number, or null if `multiple` is false."
},
voted: %Schema{
type: :boolean,
nullable: true,
description:
"When called with a user token, has the authorized user voted? Boolean, or null if no current user."
},
emojis: %Schema{
type: :array,
items: Emoji,
description: "Custom emoji to be used for rendering poll options."
},
options: %Schema{ options: %Schema{
type: :array, type: :array,
items: %Schema{ items: %Schema{
title: "PollOption",
type: :object, type: :object,
properties: %{ properties: %{
title: %Schema{type: :string}, title: %Schema{type: :string},
votes_count: %Schema{type: :integer} votes_count: %Schema{type: :integer}
} }
},
description: "Possible answers for the poll."
} }
},
example: %{
id: "34830",
expires_at: "2019-12-05T04:05:08.302Z",
expired: true,
multiple: false,
votes_count: 10,
voters_count: nil,
voted: true,
own_votes: [
1
],
options: [
%{
title: "accept",
votes_count: 6
},
%{
title: "deny",
votes_count: 4
} }
],
emojis: []
} }
}) })
end end

View file

@ -15,6 +15,8 @@ defmodule Pleroma.Web.MastodonAPI.PollController do
action_fallback(Pleroma.Web.MastodonAPI.FallbackController) action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
plug(Pleroma.Web.ApiSpec.CastAndValidate)
plug( plug(
OAuthScopesPlug, OAuthScopesPlug,
%{scopes: ["read:statuses"], fallback: :proceed_unauthenticated} when action == :show %{scopes: ["read:statuses"], fallback: :proceed_unauthenticated} when action == :show
@ -22,8 +24,10 @@ defmodule Pleroma.Web.MastodonAPI.PollController do
plug(OAuthScopesPlug, %{scopes: ["write:statuses"]} when action == :vote) plug(OAuthScopesPlug, %{scopes: ["write:statuses"]} when action == :vote)
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.PollOperation
@doc "GET /api/v1/polls/:id" @doc "GET /api/v1/polls/:id"
def show(%{assigns: %{user: user}} = conn, %{"id" => id}) do def show(%{assigns: %{user: user}} = conn, %{id: id}) do
with %Object{} = object <- Object.get_by_id_and_maybe_refetch(id, interval: 60), 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"]), %Activity{} = activity <- Activity.get_create_by_object_ap_id(object.data["id"]),
true <- Visibility.visible_for_user?(activity, user) do true <- Visibility.visible_for_user?(activity, user) do
@ -35,7 +39,7 @@ def show(%{assigns: %{user: user}} = conn, %{"id" => id}) do
end end
@doc "POST /api/v1/polls/:id/votes" @doc "POST /api/v1/polls/:id/votes"
def vote(%{assigns: %{user: user}} = conn, %{"id" => id, "choices" => choices}) do def vote(%{assigns: %{user: user}, body_params: %{choices: choices}} = conn, %{id: id}) do
with %Object{data: %{"type" => "Question"}} = object <- Object.get_by_id(id), with %Object{data: %{"type" => "Question"}} = object <- Object.get_by_id(id),
%Activity{} = activity <- Activity.get_create_by_object_ap_id(object.data["id"]), %Activity{} = activity <- Activity.get_create_by_object_ap_id(object.data["id"]),
true <- Visibility.visible_for_user?(activity, user), true <- Visibility.visible_for_user?(activity, user),

View file

@ -24,7 +24,7 @@ test "returns poll entity for object id", %{user: user, conn: conn} do
conn = get(conn, "/api/v1/polls/#{object.id}") conn = get(conn, "/api/v1/polls/#{object.id}")
response = json_response(conn, 200) response = json_response_and_validate_schema(conn, 200)
id = to_string(object.id) id = to_string(object.id)
assert %{"id" => ^id, "expired" => false, "multiple" => false} = response assert %{"id" => ^id, "expired" => false, "multiple" => false} = response
end end
@ -43,7 +43,7 @@ test "does not expose polls for private statuses", %{conn: conn} do
conn = get(conn, "/api/v1/polls/#{object.id}") conn = get(conn, "/api/v1/polls/#{object.id}")
assert json_response(conn, 404) assert json_response_and_validate_schema(conn, 404)
end end
end end
@ -65,9 +65,12 @@ test "votes are added to the poll", %{conn: conn} do
object = Object.normalize(activity) object = Object.normalize(activity)
conn = post(conn, "/api/v1/polls/#{object.id}/votes", %{"choices" => [0, 1, 2]}) conn =
conn
|> put_req_header("content-type", "application/json")
|> post("/api/v1/polls/#{object.id}/votes", %{"choices" => [0, 1, 2]})
assert json_response(conn, 200) assert json_response_and_validate_schema(conn, 200)
object = Object.get_by_id(object.id) object = Object.get_by_id(object.id)
assert Enum.all?(object.data["anyOf"], fn %{"replies" => %{"totalItems" => total_items}} -> assert Enum.all?(object.data["anyOf"], fn %{"replies" => %{"totalItems" => total_items}} ->
@ -85,8 +88,9 @@ test "author can't vote", %{user: user, conn: conn} do
object = Object.normalize(activity) object = Object.normalize(activity)
assert conn assert conn
|> put_req_header("content-type", "application/json")
|> post("/api/v1/polls/#{object.id}/votes", %{"choices" => [1]}) |> post("/api/v1/polls/#{object.id}/votes", %{"choices" => [1]})
|> json_response(422) == %{"error" => "Poll's author can't vote"} |> json_response_and_validate_schema(422) == %{"error" => "Poll's author can't vote"}
object = Object.get_by_id(object.id) object = Object.get_by_id(object.id)
@ -105,8 +109,9 @@ test "does not allow multiple choices on a single-choice question", %{conn: conn
object = Object.normalize(activity) object = Object.normalize(activity)
assert conn assert conn
|> put_req_header("content-type", "application/json")
|> post("/api/v1/polls/#{object.id}/votes", %{"choices" => [0, 1]}) |> post("/api/v1/polls/#{object.id}/votes", %{"choices" => [0, 1]})
|> json_response(422) == %{"error" => "Too many choices"} |> json_response_and_validate_schema(422) == %{"error" => "Too many choices"}
object = Object.get_by_id(object.id) object = Object.get_by_id(object.id)
@ -126,15 +131,21 @@ test "does not allow choice index to be greater than options count", %{conn: con
object = Object.normalize(activity) object = Object.normalize(activity)
conn = post(conn, "/api/v1/polls/#{object.id}/votes", %{"choices" => [2]}) conn =
conn
|> put_req_header("content-type", "application/json")
|> post("/api/v1/polls/#{object.id}/votes", %{"choices" => [2]})
assert json_response(conn, 422) == %{"error" => "Invalid indices"} assert json_response_and_validate_schema(conn, 422) == %{"error" => "Invalid indices"}
end end
test "returns 404 error when object is not exist", %{conn: conn} do test "returns 404 error when object is not exist", %{conn: conn} do
conn = post(conn, "/api/v1/polls/1/votes", %{"choices" => [0]}) conn =
conn
|> put_req_header("content-type", "application/json")
|> post("/api/v1/polls/1/votes", %{"choices" => [0]})
assert json_response(conn, 404) == %{"error" => "Record not found"} assert json_response_and_validate_schema(conn, 404) == %{"error" => "Record not found"}
end end
test "returns 404 when poll is private and not available for user", %{conn: conn} do test "returns 404 when poll is private and not available for user", %{conn: conn} do
@ -149,9 +160,12 @@ test "returns 404 when poll is private and not available for user", %{conn: conn
object = Object.normalize(activity) object = Object.normalize(activity)
conn = post(conn, "/api/v1/polls/#{object.id}/votes", %{"choices" => [0]}) conn =
conn
|> put_req_header("content-type", "application/json")
|> post("/api/v1/polls/#{object.id}/votes", %{"choices" => [0]})
assert json_response(conn, 404) == %{"error" => "Record not found"} assert json_response_and_validate_schema(conn, 404) == %{"error" => "Record not found"}
end end
end end
end end