From 6ba25d11973e56008e5d674313421197ff418d6d Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn <egor@kislitsyn.com> Date: Tue, 5 May 2020 23:19:16 +0400 Subject: [PATCH 1/2] Add Attachment schema --- .../web/api_spec/schemas/attachment.ex | 68 +++++++++++++++++++ .../web/api_spec/schemas/scheduled_status.ex | 53 +++++++++++++++ lib/pleroma/web/api_spec/schemas/status.ex | 18 +---- 3 files changed, 123 insertions(+), 16 deletions(-) create mode 100644 lib/pleroma/web/api_spec/schemas/attachment.ex create mode 100644 lib/pleroma/web/api_spec/schemas/scheduled_status.ex diff --git a/lib/pleroma/web/api_spec/schemas/attachment.ex b/lib/pleroma/web/api_spec/schemas/attachment.ex new file mode 100644 index 000000000..c146c416e --- /dev/null +++ b/lib/pleroma/web/api_spec/schemas/attachment.ex @@ -0,0 +1,68 @@ +# 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.Schemas.Attachment do + alias OpenApiSpex.Schema + + require OpenApiSpex + + OpenApiSpex.schema(%{ + title: "Attachment", + description: "Represents a file or media attachment that can be added to a status.", + type: :object, + requried: [:id, :url, :preview_url], + properties: %{ + id: %Schema{type: :string}, + url: %Schema{ + type: :string, + format: :uri, + description: "The location of the original full-size attachment" + }, + remote_url: %Schema{ + type: :string, + format: :uri, + description: + "The location of the full-size original attachment on the remote website. String (URL), or null if the attachment is local", + nullable: true + }, + preview_url: %Schema{ + type: :string, + format: :uri, + description: "The location of a scaled-down preview of the attachment" + }, + text_url: %Schema{ + type: :string, + format: :uri, + description: "A shorter URL for the attachment" + }, + description: %Schema{ + type: :string, + nullable: true, + description: + "Alternate text that describes what is in the media attachment, to be used for the visually impaired or when media attachments do not load" + }, + type: %Schema{ + type: :string, + enum: ["image", "video", "audio", "unknown"], + description: "The type of the attachment" + }, + pleroma: %Schema{ + type: :object, + properties: %{ + mime_type: %Schema{type: :string, description: "mime type of the attachment"} + } + } + }, + example: %{ + id: "1638338801", + type: "image", + url: "someurl", + remote_url: "someurl", + preview_url: "someurl", + text_url: "someurl", + description: nil, + pleroma: %{mime_type: "image/png"} + } + }) +end diff --git a/lib/pleroma/web/api_spec/schemas/scheduled_status.ex b/lib/pleroma/web/api_spec/schemas/scheduled_status.ex new file mode 100644 index 000000000..f0bc4ee3c --- /dev/null +++ b/lib/pleroma/web/api_spec/schemas/scheduled_status.ex @@ -0,0 +1,53 @@ +# 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.Schemas.ScheduledStatus do + alias OpenApiSpex.Schema + alias Pleroma.Web.ApiSpec.Schemas.VisibilityScope + alias Pleroma.Web.ApiSpec.Schemas.Poll + + require OpenApiSpex + + OpenApiSpex.schema(%{ + title: "ScheduledStatus", + description: "Represents a status that will be published at a future scheduled date.", + type: :object, + required: [:id, :scheduled_at, :params], + properties: %{ + id: %Schema{type: :string}, + scheduled_at: %Schema{type: :string, format: :"date-time"}, + media_attachments: %Schema{type: :array, format: :"date-time"}, + params: %Schema{ + type: :object, + required: [:text, :visibility], + properties: %{ + text: %Schema{type: :string, nullable: true}, + media_ids: %Schema{type: :array, nullable: true, items: %Schema{type: :string}}, + sensitive: %Schema{type: :boolean, nullable: true}, + spoiler_text: %Schema{type: :string, nullable: true}, + visibility: %Schema{type: VisibilityScope, nullable: true}, + scheduled_at: %Schema{type: :string, format: :"date-time", nullable: true}, + poll: %Schema{type: Poll, nullable: true}, + in_reply_to_id: %Schema{type: :string, nullable: true} + } + } + }, + example: %{ + id: "3221", + scheduled_at: "2019-12-05T12:33:01.000Z", + params: %{ + text: "test content", + media_ids: nil, + sensitive: nil, + spoiler_text: nil, + visibility: nil, + scheduled_at: nil, + poll: nil, + idempotency: nil, + in_reply_to_id: nil + }, + media_attachments: [] + } + }) +end diff --git a/lib/pleroma/web/api_spec/schemas/status.ex b/lib/pleroma/web/api_spec/schemas/status.ex index aef0588d4..d44636a48 100644 --- a/lib/pleroma/web/api_spec/schemas/status.ex +++ b/lib/pleroma/web/api_spec/schemas/status.ex @@ -5,6 +5,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Status do alias OpenApiSpex.Schema alias Pleroma.Web.ApiSpec.Schemas.Account + alias Pleroma.Web.ApiSpec.Schemas.Attachment alias Pleroma.Web.ApiSpec.Schemas.Emoji alias Pleroma.Web.ApiSpec.Schemas.FlakeID alias Pleroma.Web.ApiSpec.Schemas.Poll @@ -50,22 +51,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Status do language: %Schema{type: :string, nullable: true}, media_attachments: %Schema{ type: :array, - items: %Schema{ - type: :object, - properties: %{ - id: %Schema{type: :string}, - url: %Schema{type: :string, format: :uri}, - remote_url: %Schema{type: :string, format: :uri}, - preview_url: %Schema{type: :string, format: :uri}, - text_url: %Schema{type: :string, format: :uri}, - description: %Schema{type: :string}, - type: %Schema{type: :string, enum: ["image", "video", "audio", "unknown"]}, - pleroma: %Schema{ - type: :object, - properties: %{mime_type: %Schema{type: :string}} - } - } - } + items: Attachment }, mentions: %Schema{ type: :array, From 332e016bcdbda5dca90d916bc62a9c67544b5323 Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn <egor@kislitsyn.com> Date: Tue, 5 May 2020 23:42:18 +0400 Subject: [PATCH 2/2] Add OpenAPI spec for ScheduledActivityController --- .../scheduled_activity_operation.ex | 96 +++++++++++++++++++ .../web/api_spec/schemas/scheduled_status.ex | 7 +- .../scheduled_activity_controller.ex | 12 ++- test/support/helpers.ex | 8 +- .../scheduled_activity_controller_test.exs | 34 ++++--- .../mastodon_api/views/status_view_test.exs | 8 +- 6 files changed, 144 insertions(+), 21 deletions(-) create mode 100644 lib/pleroma/web/api_spec/operations/scheduled_activity_operation.ex diff --git a/lib/pleroma/web/api_spec/operations/scheduled_activity_operation.ex b/lib/pleroma/web/api_spec/operations/scheduled_activity_operation.ex new file mode 100644 index 000000000..fe675a923 --- /dev/null +++ b/lib/pleroma/web/api_spec/operations/scheduled_activity_operation.ex @@ -0,0 +1,96 @@ +# 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.ScheduledActivityOperation do + alias OpenApiSpex.Operation + alias OpenApiSpex.Schema + alias Pleroma.Web.ApiSpec.Schemas.ApiError + alias Pleroma.Web.ApiSpec.Schemas.FlakeID + alias Pleroma.Web.ApiSpec.Schemas.ScheduledStatus + + import Pleroma.Web.ApiSpec.Helpers + + def open_api_operation(action) do + operation = String.to_existing_atom("#{action}_operation") + apply(__MODULE__, operation, []) + end + + def index_operation do + %Operation{ + tags: ["Scheduled Statuses"], + summary: "View scheduled statuses", + security: [%{"oAuth" => ["read:statuses"]}], + parameters: pagination_params(), + operationId: "ScheduledActivity.index", + responses: %{ + 200 => + Operation.response("Array of ScheduledStatus", "application/json", %Schema{ + type: :array, + items: ScheduledStatus + }) + } + } + end + + def show_operation do + %Operation{ + tags: ["Scheduled Statuses"], + summary: "View a single scheduled status", + security: [%{"oAuth" => ["read:statuses"]}], + parameters: [id_param()], + operationId: "ScheduledActivity.show", + responses: %{ + 200 => Operation.response("Scheduled Status", "application/json", ScheduledStatus), + 404 => Operation.response("Error", "application/json", ApiError) + } + } + end + + def update_operation do + %Operation{ + tags: ["Scheduled Statuses"], + summary: "Schedule a status", + operationId: "ScheduledActivity.update", + security: [%{"oAuth" => ["write:statuses"]}], + parameters: [id_param()], + requestBody: + request_body("Parameters", %Schema{ + type: :object, + properties: %{ + scheduled_at: %Schema{ + type: :string, + format: :"date-time", + description: + "ISO 8601 Datetime at which the status will be published. Must be at least 5 minutes into the future." + } + } + }), + responses: %{ + 200 => Operation.response("Scheduled Status", "application/json", ScheduledStatus), + 404 => Operation.response("Error", "application/json", ApiError) + } + } + end + + def delete_operation do + %Operation{ + tags: ["Scheduled Statuses"], + summary: "Cancel a scheduled status", + security: [%{"oAuth" => ["write:statuses"]}], + parameters: [id_param()], + operationId: "ScheduledActivity.delete", + responses: %{ + 200 => Operation.response("Empty object", "application/json", %Schema{type: :object}), + 404 => Operation.response("Error", "application/json", ApiError) + } + } + end + + defp id_param do + Operation.parameter(:id, :path, FlakeID, "Poll ID", + example: "123", + required: true + ) + end +end diff --git a/lib/pleroma/web/api_spec/schemas/scheduled_status.ex b/lib/pleroma/web/api_spec/schemas/scheduled_status.ex index f0bc4ee3c..0520d0848 100644 --- a/lib/pleroma/web/api_spec/schemas/scheduled_status.ex +++ b/lib/pleroma/web/api_spec/schemas/scheduled_status.ex @@ -4,8 +4,9 @@ defmodule Pleroma.Web.ApiSpec.Schemas.ScheduledStatus do alias OpenApiSpex.Schema - alias Pleroma.Web.ApiSpec.Schemas.VisibilityScope + alias Pleroma.Web.ApiSpec.Schemas.Attachment alias Pleroma.Web.ApiSpec.Schemas.Poll + alias Pleroma.Web.ApiSpec.Schemas.VisibilityScope require OpenApiSpex @@ -17,7 +18,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.ScheduledStatus do properties: %{ id: %Schema{type: :string}, scheduled_at: %Schema{type: :string, format: :"date-time"}, - media_attachments: %Schema{type: :array, format: :"date-time"}, + media_attachments: %Schema{type: :array, items: Attachment}, params: %Schema{ type: :object, required: [:text, :visibility], @@ -47,7 +48,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.ScheduledStatus do idempotency: nil, in_reply_to_id: nil }, - media_attachments: [] + media_attachments: [Attachment.schema().example] } }) end diff --git a/lib/pleroma/web/mastodon_api/controllers/scheduled_activity_controller.ex b/lib/pleroma/web/mastodon_api/controllers/scheduled_activity_controller.ex index 899b78873..1719c67ea 100644 --- a/lib/pleroma/web/mastodon_api/controllers/scheduled_activity_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/scheduled_activity_controller.ex @@ -11,17 +11,21 @@ defmodule Pleroma.Web.MastodonAPI.ScheduledActivityController do alias Pleroma.ScheduledActivity alias Pleroma.Web.MastodonAPI.MastodonAPI - plug(:assign_scheduled_activity when action != :index) - @oauth_read_actions [:show, :index] + plug(Pleroma.Web.ApiSpec.CastAndValidate) plug(OAuthScopesPlug, %{scopes: ["read:statuses"]} when action in @oauth_read_actions) plug(OAuthScopesPlug, %{scopes: ["write:statuses"]} when action not in @oauth_read_actions) + plug(:assign_scheduled_activity when action != :index) action_fallback(Pleroma.Web.MastodonAPI.FallbackController) + defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.ScheduledActivityOperation + @doc "GET /api/v1/scheduled_statuses" def index(%{assigns: %{user: user}} = conn, params) do + params = Map.new(params, fn {key, value} -> {to_string(key), value} end) + with scheduled_activities <- MastodonAPI.get_scheduled_activities(user, params) do conn |> add_link_headers(scheduled_activities) @@ -35,7 +39,7 @@ def show(%{assigns: %{scheduled_activity: scheduled_activity}} = conn, _params) end @doc "PUT /api/v1/scheduled_statuses/:id" - def update(%{assigns: %{scheduled_activity: scheduled_activity}} = conn, params) do + def update(%{assigns: %{scheduled_activity: scheduled_activity}, body_params: params} = conn, _) do with {:ok, scheduled_activity} <- ScheduledActivity.update(scheduled_activity, params) do render(conn, "show.json", scheduled_activity: scheduled_activity) end @@ -48,7 +52,7 @@ def delete(%{assigns: %{scheduled_activity: scheduled_activity}} = conn, _params end end - defp assign_scheduled_activity(%{assigns: %{user: user}, params: %{"id" => id}} = conn, _) do + defp assign_scheduled_activity(%{assigns: %{user: user}, params: %{id: id}} = conn, _) do case ScheduledActivity.get(user, id) do %ScheduledActivity{} = activity -> assign(conn, :scheduled_activity, activity) nil -> Pleroma.Web.MastodonAPI.FallbackController.call(conn, {:error, :not_found}) |> halt() diff --git a/test/support/helpers.ex b/test/support/helpers.ex index e68e9bfd2..26281b45e 100644 --- a/test/support/helpers.ex +++ b/test/support/helpers.ex @@ -40,12 +40,18 @@ defmacro __using__(_opts) do clear_config: 2 ] - def to_datetime(naive_datetime) do + def to_datetime(%NaiveDateTime{} = naive_datetime) do naive_datetime |> DateTime.from_naive!("Etc/UTC") |> DateTime.truncate(:second) end + def to_datetime(datetime) when is_binary(datetime) do + datetime + |> NaiveDateTime.from_iso8601!() + |> to_datetime() + end + def collect_ids(collection) do collection |> Enum.map(& &1.id) diff --git a/test/web/mastodon_api/controllers/scheduled_activity_controller_test.exs b/test/web/mastodon_api/controllers/scheduled_activity_controller_test.exs index f86274d57..1ff871c89 100644 --- a/test/web/mastodon_api/controllers/scheduled_activity_controller_test.exs +++ b/test/web/mastodon_api/controllers/scheduled_activity_controller_test.exs @@ -24,19 +24,19 @@ test "shows scheduled activities" do # min_id conn_res = get(conn, "/api/v1/scheduled_statuses?limit=2&min_id=#{scheduled_activity_id1}") - result = json_response(conn_res, 200) + result = json_response_and_validate_schema(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) + result = json_response_and_validate_schema(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) + result = json_response_and_validate_schema(conn_res, 200) assert [%{"id" => ^scheduled_activity_id3}, %{"id" => ^scheduled_activity_id2}] = result end @@ -46,12 +46,12 @@ test "shows a scheduled activity" do res_conn = get(conn, "/api/v1/scheduled_statuses/#{scheduled_activity.id}") - assert %{"id" => scheduled_activity_id} = json_response(res_conn, 200) + assert %{"id" => scheduled_activity_id} = json_response_and_validate_schema(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) + assert %{"error" => "Record not found"} = json_response_and_validate_schema(res_conn, 404) end test "updates a scheduled activity" do @@ -74,22 +74,32 @@ test "updates a scheduled activity" do assert job.args == %{"activity_id" => scheduled_activity.id} assert DateTime.truncate(job.scheduled_at, :second) == to_datetime(scheduled_at) - new_scheduled_at = Timex.shift(NaiveDateTime.utc_now(), minutes: 120) + new_scheduled_at = + NaiveDateTime.utc_now() + |> Timex.shift(minutes: 120) + |> Timex.format!("%Y-%m-%dT%H:%M:%S.%fZ", :strftime) res_conn = - put(conn, "/api/v1/scheduled_statuses/#{scheduled_activity.id}", %{ + conn + |> put_req_header("content-type", "application/json") + |> 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 %{"scheduled_at" => expected_scheduled_at} = + json_response_and_validate_schema(res_conn, 200) + assert expected_scheduled_at == Pleroma.Web.CommonAPI.Utils.to_masto_date(new_scheduled_at) job = refresh_record(job) assert DateTime.truncate(job.scheduled_at, :second) == to_datetime(new_scheduled_at) - res_conn = put(conn, "/api/v1/scheduled_statuses/404", %{scheduled_at: new_scheduled_at}) + res_conn = + conn + |> put_req_header("content-type", "application/json") + |> put("/api/v1/scheduled_statuses/404", %{scheduled_at: new_scheduled_at}) - assert %{"error" => "Record not found"} = json_response(res_conn, 404) + assert %{"error" => "Record not found"} = json_response_and_validate_schema(res_conn, 404) end test "deletes a scheduled activity" do @@ -115,7 +125,7 @@ test "deletes a scheduled activity" do |> assign(:user, user) |> delete("/api/v1/scheduled_statuses/#{scheduled_activity.id}") - assert %{} = json_response(res_conn, 200) + assert %{} = json_response_and_validate_schema(res_conn, 200) refute Repo.get(ScheduledActivity, scheduled_activity.id) refute Repo.get(Oban.Job, job.id) @@ -124,6 +134,6 @@ test "deletes a scheduled activity" do |> assign(:user, user) |> delete("/api/v1/scheduled_statuses/#{scheduled_activity.id}") - assert %{"error" => "Record not found"} = json_response(res_conn, 404) + assert %{"error" => "Record not found"} = json_response_and_validate_schema(res_conn, 404) 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 6791c2fb0..451723e60 100644 --- a/test/web/mastodon_api/views/status_view_test.exs +++ b/test/web/mastodon_api/views/status_view_test.exs @@ -402,11 +402,17 @@ test "attachments" do pleroma: %{mime_type: "image/png"} } + api_spec = Pleroma.Web.ApiSpec.spec() + assert expected == StatusView.render("attachment.json", %{attachment: object}) + OpenApiSpex.TestAssertions.assert_schema(expected, "Attachment", api_spec) # If theres a "id", use that instead of the generated one object = Map.put(object, "id", 2) - assert %{id: "2"} = StatusView.render("attachment.json", %{attachment: object}) + result = StatusView.render("attachment.json", %{attachment: object}) + + assert %{id: "2"} = result + OpenApiSpex.TestAssertions.assert_schema(result, "Attachment", api_spec) end test "put the url advertised in the Activity in to the url attribute" do