Add spec for AccountController.update_credentials
authorEgor Kislitsyn <egor@kislitsyn.com>
Tue, 7 Apr 2020 10:53:12 +0000 (14:53 +0400)
committerEgor Kislitsyn <egor@kislitsyn.com>
Mon, 13 Apr 2020 14:16:07 +0000 (18:16 +0400)
lib/pleroma/web/api_spec/helpers.ex
lib/pleroma/web/api_spec/operations/account_operation.ex
lib/pleroma/web/api_spec/schemas/account_field_attribute.ex [new file with mode: 0644]
lib/pleroma/web/api_spec/schemas/account_update_credentials_request.ex [new file with mode: 0644]
lib/pleroma/web/mastodon_api/controllers/account_controller.ex
test/support/conn_case.ex
test/web/api_spec/account_operation_test.exs
test/web/mastodon_api/controllers/account_controller/update_credentials_test.exs

index 35cf4c0d8313e7c9ff0712f23eef462e593dd4d2..7348dcbee6c5d32a667f7e6a9a1e565627d486d5 100644 (file)
@@ -4,7 +4,7 @@
 
 defmodule Pleroma.Web.ApiSpec.Helpers do
   def request_body(description, schema_ref, opts \\ []) do
-    media_types = ["application/json", "multipart/form-data"]
+    media_types = ["application/json", "multipart/form-data", "application/x-www-form-urlencoded"]
 
     content =
       media_types
index 3d2270c29007932eaf36091564e5149dea2e4876..d7b56cc2b86f63bfe6f1ffcb27981d539996d970 100644 (file)
@@ -8,6 +8,7 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
   alias Pleroma.Web.ApiSpec.Schemas.Account
   alias Pleroma.Web.ApiSpec.Schemas.AccountCreateRequest
   alias Pleroma.Web.ApiSpec.Schemas.AccountCreateResponse
+  alias Pleroma.Web.ApiSpec.Schemas.AccountUpdateCredentialsRequest
 
   @spec open_api_operation(atom) :: Operation.t()
   def open_api_operation(action) do
@@ -44,7 +45,18 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
   end
 
   def update_credentials_operation do
-    :ok
+    %Operation{
+      tags: ["accounts"],
+      summary: "Update account credentials",
+      description: "Update the user's display and preferences.",
+      operationId: "AccountController.update_credentials",
+      security: [%{"oAuth" => ["write:accounts"]}],
+      requestBody:
+        Helpers.request_body("Parameters", AccountUpdateCredentialsRequest, required: true),
+      responses: %{
+        200 => Operation.response("Account", "application/json", Account)
+      }
+    }
   end
 
   def relationships_operation do
diff --git a/lib/pleroma/web/api_spec/schemas/account_field_attribute.ex b/lib/pleroma/web/api_spec/schemas/account_field_attribute.ex
new file mode 100644 (file)
index 0000000..fbbdf95
--- /dev/null
@@ -0,0 +1,26 @@
+# 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.AccountAttributeField do
+  alias OpenApiSpex.Schema
+
+  require OpenApiSpex
+
+  OpenApiSpex.schema(%{
+    title: "AccountAttributeField",
+    description: "Request schema for account custom fields",
+    type: :object,
+    properties: %{
+      name: %Schema{type: :string},
+      value: %Schema{type: :string}
+    },
+    required: [:name, :value],
+    example: %{
+      "JSON" => %{
+        "name" => "Website",
+        "value" => "https://pleroma.com"
+      }
+    }
+  })
+end
diff --git a/lib/pleroma/web/api_spec/schemas/account_update_credentials_request.ex b/lib/pleroma/web/api_spec/schemas/account_update_credentials_request.ex
new file mode 100644 (file)
index 0000000..a50bce5
--- /dev/null
@@ -0,0 +1,123 @@
+# 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.AccountUpdateCredentialsRequest do
+  alias OpenApiSpex.Schema
+  alias Pleroma.Web.ApiSpec.Schemas.AccountAttributeField
+  require OpenApiSpex
+
+  OpenApiSpex.schema(%{
+    title: "AccountUpdateCredentialsRequest",
+    description: "POST body for creating an account",
+    type: :object,
+    properties: %{
+      bot: %Schema{
+        type: :boolean,
+        description: "Whether the account has a bot flag."
+      },
+      display_name: %Schema{
+        type: :string,
+        description: "The display name to use for the profile."
+      },
+      note: %Schema{type: :string, description: "The account bio."},
+      avatar: %Schema{
+        type: :string,
+        description: "Avatar image encoded using multipart/form-data",
+        format: :binary
+      },
+      header: %Schema{
+        type: :string,
+        description: "Header image encoded using multipart/form-data",
+        format: :binary
+      },
+      locked: %Schema{
+        type: :boolean,
+        description: "Whether manual approval of follow requests is required."
+      },
+      fields_attributes: %Schema{
+        oneOf: [%Schema{type: :array, items: AccountAttributeField}, %Schema{type: :object}]
+      },
+      # NOTE: `source` field is not supported
+      #
+      # source: %Schema{
+      #   type: :object,
+      #   properties: %{
+      #     privacy: %Schema{type: :string},
+      #     sensitive: %Schema{type: :boolean},
+      #     language: %Schema{type: :string}
+      #   }
+      # },
+
+      # Pleroma-specific fields
+      no_rich_text: %Schema{
+        type: :boolean,
+        description: "html tags are stripped from all statuses requested from the API"
+      },
+      hide_followers: %Schema{type: :boolean, description: "user's followers will be hidden"},
+      hide_follows: %Schema{type: :boolean, description: "user's follows will be hidden"},
+      hide_followers_count: %Schema{
+        type: :boolean,
+        description: "user's follower count will be hidden"
+      },
+      hide_follows_count: %Schema{
+        type: :boolean,
+        description: "user's follow count will be hidden"
+      },
+      hide_favorites: %Schema{
+        type: :boolean,
+        description: "user's favorites timeline will be hidden"
+      },
+      show_role: %Schema{
+        type: :boolean,
+        description: "user's role (e.g admin, moderator) will be exposed to anyone in the
+      API"
+      },
+      default_scope: %Schema{
+        type: :string,
+        description: "The scope returned under privacy key in Source subentity"
+      },
+      pleroma_settings_store: %Schema{
+        type: :object,
+        description: "Opaque user settings to be saved on the backend."
+      },
+      skip_thread_containment: %Schema{
+        type: :boolean,
+        description: "Skip filtering out broken threads"
+      },
+      allow_following_move: %Schema{
+        type: :boolean,
+        description: "Allows automatically follow moved following accounts"
+      },
+      pleroma_background_image: %Schema{
+        type: :string,
+        description: "Sets the background image of the user.",
+        format: :binary
+      },
+      discoverable: %Schema{
+        type: :boolean,
+        description: "Discovery of this account in search results and other services is allowed."
+      },
+      actor_type: %Schema{type: :string, description: "the type of this account."}
+    },
+    example: %{
+      bot: false,
+      display_name: "cofe",
+      note: "foobar",
+      fields_attributes: [%{name: "foo", value: "bar"}],
+      no_rich_text: false,
+      hide_followers: true,
+      hide_follows: false,
+      hide_followers_count: false,
+      hide_follows_count: false,
+      hide_favorites: false,
+      show_role: false,
+      default_scope: "private",
+      pleroma_settings_store: %{"pleroma-fe" => %{"key" => "val"}},
+      skip_thread_containment: false,
+      allow_following_move: false,
+      discoverable: false,
+      actor_type: "Person"
+    }
+  })
+end
index eb082daf895fb9703ae1012ca15150910ab2a034..9c986b3b20c50236242ecdb759695c84f9faa3a7 100644 (file)
@@ -82,7 +82,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
 
   plug(
     OpenApiSpex.Plug.CastAndValidate,
-    [render_error: Pleroma.Web.ApiSpec.RenderError] when action == :create
+    [render_error: Pleroma.Web.ApiSpec.RenderError]
+    when action in [:create, :verify_credentials, :update_credentials]
   )
 
   action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
@@ -152,9 +153,15 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
   end
 
   @doc "PATCH /api/v1/accounts/update_credentials"
-  def update_credentials(%{assigns: %{user: original_user}} = conn, params) do
+  def update_credentials(%{assigns: %{user: original_user}, body_params: params} = conn, _params) do
     user = original_user
 
+    params =
+      params
+      |> Map.from_struct()
+      |> Enum.filter(fn {_, value} -> not is_nil(value) end)
+      |> Enum.into(%{})
+
     user_params =
       [
         :no_rich_text,
@@ -170,22 +177,22 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
         :discoverable
       ]
       |> Enum.reduce(%{}, fn key, acc ->
-        add_if_present(acc, params, to_string(key), key, &{:ok, truthy_param?(&1)})
+        add_if_present(acc, params, key, key, &{:ok, truthy_param?(&1)})
       end)
-      |> add_if_present(params, "display_name", :name)
-      |> add_if_present(params, "note", :bio)
-      |> add_if_present(params, "avatar", :avatar)
-      |> add_if_present(params, "header", :banner)
-      |> add_if_present(params, "pleroma_background_image", :background)
+      |> add_if_present(params, :display_name, :name)
+      |> add_if_present(params, :note, :bio)
+      |> add_if_present(params, :avatar, :avatar)
+      |> add_if_present(params, :header, :banner)
+      |> add_if_present(params, :pleroma_background_image, :background)
       |> add_if_present(
         params,
-        "fields_attributes",
+        :fields_attributes,
         :raw_fields,
         &{:ok, normalize_fields_attributes(&1)}
       )
-      |> add_if_present(params, "pleroma_settings_store", :pleroma_settings_store)
-      |> add_if_present(params, "default_scope", :default_scope)
-      |> add_if_present(params, "actor_type", :actor_type)
+      |> add_if_present(params, :pleroma_settings_store, :pleroma_settings_store)
+      |> add_if_present(params, :default_scope, :default_scope)
+      |> add_if_present(params, :actor_type, :actor_type)
 
     changeset = User.update_changeset(user, user_params)
 
@@ -200,7 +207,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
 
   defp add_if_present(map, params, params_field, map_field, value_function \\ &{:ok, &1}) do
     with true <- Map.has_key?(params, params_field),
-         {:ok, new_value} <- value_function.(params[params_field]) do
+         {:ok, new_value} <- value_function.(Map.get(params, params_field)) do
       Map.put(map, map_field, new_value)
     else
       _ -> map
@@ -211,7 +218,13 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
     if Enum.all?(fields, &is_tuple/1) do
       Enum.map(fields, fn {_, v} -> v end)
     else
-      fields
+      Enum.map(fields, fn
+        %Pleroma.Web.ApiSpec.Schemas.AccountAttributeField{} = field ->
+          %{"name" => field.name, "value" => field.value}
+
+        field ->
+          field
+      end)
     end
   end
 
index 06487420158ebed5412c15aa05800f4823207e6e..36ce372c29f6394c31a50c5af0870f79375df4f3 100644 (file)
@@ -51,6 +51,11 @@ defmodule Pleroma.Web.ConnCase do
         %{user: user, token: token, conn: conn}
       end
 
+      defp request_content_type(%{conn: conn}) do
+        conn = put_req_header(conn, "content-type", "multipart/form-data")
+        [conn: conn]
+      end
+
       defp ensure_federating_or_authenticated(conn, url, user) do
         initial_setting = Config.get([:instance, :federating])
         on_exit(fn -> Config.put([:instance, :federating], initial_setting) end)
index 37501b8cc71284a1b028cff4d234e62ecf3843ce..a540590743d27555fc25a824eec1863630b63fbd 100644 (file)
@@ -9,6 +9,7 @@ defmodule Pleroma.Web.ApiSpec.AccountOperationTest do
   alias Pleroma.Web.ApiSpec.Schemas.Account
   alias Pleroma.Web.ApiSpec.Schemas.AccountCreateRequest
   alias Pleroma.Web.ApiSpec.Schemas.AccountCreateResponse
+  alias Pleroma.Web.ApiSpec.Schemas.AccountUpdateCredentialsRequest
 
   import OpenApiSpex.TestAssertions
   import Pleroma.Factory
@@ -31,6 +32,12 @@ defmodule Pleroma.Web.ApiSpec.AccountOperationTest do
     assert_schema(schema.example, "AccountCreateResponse", api_spec)
   end
 
+  test "AccountUpdateCredentialsRequest example matches schema" do
+    api_spec = ApiSpec.spec()
+    schema = AccountUpdateCredentialsRequest.schema()
+    assert_schema(schema.example, "AccountUpdateCredentialsRequest", api_spec)
+  end
+
   test "AccountController produces a AccountCreateResponse", %{conn: conn} do
     api_spec = ApiSpec.spec()
     app_token = insert(:oauth_token, user: nil)
@@ -52,4 +59,29 @@ defmodule Pleroma.Web.ApiSpec.AccountOperationTest do
 
     assert_schema(json, "AccountCreateResponse", api_spec)
   end
+
+  test "AccountUpdateCredentialsRequest produces an Account", %{conn: conn} do
+    api_spec = ApiSpec.spec()
+    token = insert(:oauth_token, scopes: ["read", "write"])
+
+    json =
+      conn
+      |> put_req_header("authorization", "Bearer " <> token.token)
+      |> put_req_header("content-type", "application/json")
+      |> patch(
+        "/api/v1/accounts/update_credentials",
+        %{
+          hide_followers_count: "true",
+          hide_follows_count: "true",
+          skip_thread_containment: "true",
+          hide_follows: "true",
+          pleroma_settings_store: %{"pleroma-fe" => %{"key" => "val"}},
+          note: "foobar",
+          fields_attributes: [%{name: "foo", value: "bar"}]
+        }
+      )
+      |> json_response(200)
+
+    assert_schema(json, "Account", api_spec)
+  end
 end
index 2d256f63c1f3e5b2284cad219a7f0c181da87f03..0e890a9807125a2c84c31df2001204fac06602a3 100644 (file)
@@ -14,6 +14,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController.UpdateCredentialsTest do
 
   describe "updating credentials" do
     setup do: oauth_access(["write:accounts"])
+    setup :request_content_type
 
     test "sets user settings in a generic way", %{conn: conn} do
       res_conn =
@@ -237,6 +238,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController.UpdateCredentialsTest do
       for token <- [token1, token2] do
         conn =
           build_conn()
+          |> put_req_header("content-type", "multipart/form-data")
           |> put_req_header("authorization", "Bearer #{token.token}")
           |> patch("/api/v1/accounts/update_credentials", %{})