From b08ded6c2f5ee29c6efc8c67cfc2ce0a679f0c77 Mon Sep 17 00:00:00 2001
From: Egor Kislitsyn <egor@kislitsyn.com>
Date: Fri, 3 Apr 2020 22:45:08 +0400
Subject: [PATCH 01/40] Add spec for AccountController.create

---
 .../api_spec/operations/account_operation.ex  |  68 ++++++
 lib/pleroma/web/api_spec/render_error.ex      |  27 +++
 .../schemas/account_create_request.ex         |  56 +++++
 .../schemas/account_create_response.ex        |  29 +++
 .../controllers/account_controller.ex         |  36 +--
 lib/pleroma/web/twitter_api/twitter_api.ex    | 102 ++++----
 test/web/api_spec/account_operation_test.exs  |  48 ++++
 .../controllers/account_controller_test.exs   |  30 ++-
 test/web/twitter_api/twitter_api_test.exs     | 222 +++++++++---------
 9 files changed, 426 insertions(+), 192 deletions(-)
 create mode 100644 lib/pleroma/web/api_spec/operations/account_operation.ex
 create mode 100644 lib/pleroma/web/api_spec/render_error.ex
 create mode 100644 lib/pleroma/web/api_spec/schemas/account_create_request.ex
 create mode 100644 lib/pleroma/web/api_spec/schemas/account_create_response.ex
 create mode 100644 test/web/api_spec/account_operation_test.exs

diff --git a/lib/pleroma/web/api_spec/operations/account_operation.ex b/lib/pleroma/web/api_spec/operations/account_operation.ex
new file mode 100644
index 000000000..9085f1af1
--- /dev/null
+++ b/lib/pleroma/web/api_spec/operations/account_operation.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.AccountOperation do
+  alias OpenApiSpex.Operation
+  alias Pleroma.Web.ApiSpec.Schemas.AccountCreateRequest
+  alias Pleroma.Web.ApiSpec.Schemas.AccountCreateResponse
+  alias Pleroma.Web.ApiSpec.Helpers
+
+  @spec open_api_operation(atom) :: Operation.t()
+  def open_api_operation(action) do
+    operation = String.to_existing_atom("#{action}_operation")
+    apply(__MODULE__, operation, [])
+  end
+
+  @spec create_operation() :: Operation.t()
+  def create_operation do
+    %Operation{
+      tags: ["accounts"],
+      summary: "Register an account",
+      description:
+        "Creates a user and account records. Returns an account access token for the app that initiated the request. The app should save this token for later, and should wait for the user to confirm their account by clicking a link in their email inbox.",
+      operationId: "AccountController.create",
+      requestBody: Helpers.request_body("Parameters", AccountCreateRequest, required: true),
+      responses: %{
+        200 => Operation.response("Account", "application/json", AccountCreateResponse)
+      }
+    }
+  end
+
+  def verify_credentials_operation do
+    :ok
+  end
+
+  def update_credentials_operation do
+    :ok
+  end
+
+  def relationships_operation do
+    :ok
+  end
+
+  def show_operation do
+    :ok
+  end
+
+  def statuses_operation do
+    :ok
+  end
+
+  def followers_operation do
+    :ok
+  end
+
+  def following_operation, do: :ok
+  def lists_operation, do: :ok
+  def follow_operation, do: :ok
+  def unfollow_operation, do: :ok
+  def mute_operation, do: :ok
+  def unmute_operation, do: :ok
+  def block_operation, do: :ok
+  def unblock_operation, do: :ok
+  def follows_operation, do: :ok
+  def mutes_operation, do: :ok
+  def blocks_operation, do: :ok
+  def endorsements_operation, do: :ok
+end
diff --git a/lib/pleroma/web/api_spec/render_error.ex b/lib/pleroma/web/api_spec/render_error.ex
new file mode 100644
index 000000000..e063d115b
--- /dev/null
+++ b/lib/pleroma/web/api_spec/render_error.ex
@@ -0,0 +1,27 @@
+# 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.RenderError do
+  @behaviour Plug
+
+  alias Plug.Conn
+  alias OpenApiSpex.Plug.JsonRenderError
+
+  @impl Plug
+  def init(opts), do: opts
+
+  @impl Plug
+
+  def call(%{private: %{open_api_spex: %{operation_id: "AccountController.create"}}} = conn, _) do
+    conn
+    |> Conn.put_status(:bad_request)
+    |> Phoenix.Controller.json(%{"error" => "Missing parameters"})
+  end
+
+  def call(conn, reason) do
+    opts = JsonRenderError.init(reason)
+
+    JsonRenderError.call(conn, opts)
+  end
+end
diff --git a/lib/pleroma/web/api_spec/schemas/account_create_request.ex b/lib/pleroma/web/api_spec/schemas/account_create_request.ex
new file mode 100644
index 000000000..398e2d613
--- /dev/null
+++ b/lib/pleroma/web/api_spec/schemas/account_create_request.ex
@@ -0,0 +1,56 @@
+# 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.AccountCreateRequest do
+  alias OpenApiSpex.Schema
+  require OpenApiSpex
+
+  OpenApiSpex.schema(%{
+    title: "AccountCreateRequest",
+    description: "POST body for creating an account",
+    type: :object,
+    properties: %{
+      reason: %Schema{
+        type: :string,
+        description:
+          "Text that will be reviewed by moderators if registrations require manual approval"
+      },
+      username: %Schema{type: :string, description: "The desired username for the account"},
+      email: %Schema{
+        type: :string,
+        description:
+          "The email address to be used for login. Required when `account_activation_required` is enabled.",
+        format: :email
+      },
+      password: %Schema{type: :string, description: "The password to be used for login"},
+      agreement: %Schema{
+        type: :boolean,
+        description:
+          "Whether the user agrees to the local rules, terms, and policies. These should be presented to the user in order to allow them to consent before setting this parameter to TRUE."
+      },
+      locale: %Schema{
+        type: :string,
+        description: "The language of the confirmation email that will be sent"
+      },
+      # Pleroma-specific properties:
+      fullname: %Schema{type: :string, description: "Full name"},
+      bio: %Schema{type: :string, description: "Bio", default: ""},
+      captcha_solution: %Schema{type: :string, description: "Provider-specific captcha solution"},
+      captcha_token: %Schema{type: :string, description: "Provider-specific captcha token"},
+      captcha_answer_data: %Schema{type: :string, description: "Provider-specific captcha data"},
+      token: %Schema{
+        type: :string,
+        description: "Invite token required when the registrations aren't public"
+      }
+    },
+    required: [:username, :password, :agreement],
+    example: %{
+      "username" => "cofe",
+      "email" => "cofe@example.com",
+      "password" => "secret",
+      "agreement" => "true",
+      "bio" => "☕️"
+    }
+  })
+end
diff --git a/lib/pleroma/web/api_spec/schemas/account_create_response.ex b/lib/pleroma/web/api_spec/schemas/account_create_response.ex
new file mode 100644
index 000000000..f41a034c0
--- /dev/null
+++ b/lib/pleroma/web/api_spec/schemas/account_create_response.ex
@@ -0,0 +1,29 @@
+# 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.AccountCreateResponse do
+  alias OpenApiSpex.Schema
+
+  require OpenApiSpex
+
+  OpenApiSpex.schema(%{
+    title: "AccountCreateResponse",
+    description: "Response schema for an account",
+    type: :object,
+    properties: %{
+      token_type: %Schema{type: :string},
+      access_token: %Schema{type: :string},
+      scope: %Schema{type: :array, items: %Schema{type: :string}},
+      created_at: %Schema{type: :integer}
+    },
+    example: %{
+      "JSON" => %{
+        "access_token" => "i9hAVVzGld86Pl5JtLtizKoXVvtTlSCJvwaugCxvZzk",
+        "created_at" => 1_585_918_714,
+        "scope" => ["read", "write", "follow", "push"],
+        "token_type" => "Bearer"
+      }
+    }
+  })
+end
diff --git a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
index 7da1a11f6..eb082daf8 100644
--- a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
@@ -80,27 +80,33 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
   plug(RateLimiter, [name: :app_account_creation] when action == :create)
   plug(:assign_account_by_id when action in @needs_account)
 
+  plug(
+    OpenApiSpex.Plug.CastAndValidate,
+    [render_error: Pleroma.Web.ApiSpec.RenderError] when action == :create
+  )
+
   action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
 
+  defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.AccountOperation
+
   @doc "POST /api/v1/accounts"
-  def create(
-        %{assigns: %{app: app}} = conn,
-        %{"username" => nickname, "password" => _, "agreement" => true} = params
-      ) do
+  def create(%{assigns: %{app: app}, body_params: params} = conn, _params) do
     params =
       params
       |> Map.take([
-        "email",
-        "captcha_solution",
-        "captcha_token",
-        "captcha_answer_data",
-        "token",
-        "password"
+        :email,
+        :bio,
+        :captcha_solution,
+        :captcha_token,
+        :captcha_answer_data,
+        :token,
+        :password,
+        :fullname
       ])
-      |> Map.put("nickname", nickname)
-      |> Map.put("fullname", params["fullname"] || nickname)
-      |> Map.put("bio", params["bio"] || "")
-      |> Map.put("confirm", params["password"])
+      |> Map.put(:nickname, params.username)
+      |> Map.put(:fullname, params.fullname || params.username)
+      |> Map.put(:bio, params.bio || "")
+      |> Map.put(:confirm, params.password)
 
     with :ok <- validate_email_param(params),
          {:ok, user} <- TwitterAPI.register_user(params, need_confirmation: true),
@@ -124,7 +130,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
     render_error(conn, :forbidden, "Invalid credentials")
   end
 
-  defp validate_email_param(%{"email" => _}), do: :ok
+  defp validate_email_param(%{:email => email}) when not is_nil(email), do: :ok
 
   defp validate_email_param(_) do
     case Pleroma.Config.get([:instance, :account_activation_required]) do
diff --git a/lib/pleroma/web/twitter_api/twitter_api.ex b/lib/pleroma/web/twitter_api/twitter_api.ex
index f9c0994da..37be48b5a 100644
--- a/lib/pleroma/web/twitter_api/twitter_api.ex
+++ b/lib/pleroma/web/twitter_api/twitter_api.ex
@@ -12,72 +12,56 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do
   require Pleroma.Constants
 
   def register_user(params, opts \\ []) do
-    token = params["token"]
+    params =
+      params
+      |> Map.take([
+        :nickname,
+        :password,
+        :captcha_solution,
+        :captcha_token,
+        :captcha_answer_data,
+        :token,
+        :email
+      ])
+      |> Map.put(:bio, User.parse_bio(params[:bio] || ""))
+      |> Map.put(:name, params.fullname)
+      |> Map.put(:password_confirmation, params[:confirm])
 
-    params = %{
-      nickname: params["nickname"],
-      name: params["fullname"],
-      bio: User.parse_bio(params["bio"]),
-      email: params["email"],
-      password: params["password"],
-      password_confirmation: params["confirm"],
-      captcha_solution: params["captcha_solution"],
-      captcha_token: params["captcha_token"],
-      captcha_answer_data: params["captcha_answer_data"]
-    }
+    case validate_captcha(params) do
+      :ok ->
+        if Pleroma.Config.get([:instance, :registrations_open]) do
+          create_user(params, opts)
+        else
+          create_user_with_invite(params, opts)
+        end
 
-    captcha_enabled = Pleroma.Config.get([Pleroma.Captcha, :enabled])
-    # true if captcha is disabled or enabled and valid, false otherwise
-    captcha_ok =
-      if not captcha_enabled do
-        :ok
-      else
-        Pleroma.Captcha.validate(
-          params[:captcha_token],
-          params[:captcha_solution],
-          params[:captcha_answer_data]
-        )
-      end
-
-    # Captcha invalid
-    if captcha_ok != :ok do
-      {:error, error} = captcha_ok
-      # I have no idea how this error handling works
-      {:error, %{error: Jason.encode!(%{captcha: [error]})}}
-    else
-      registration_process(
-        params,
-        %{
-          registrations_open: Pleroma.Config.get([:instance, :registrations_open]),
-          token: token
-        },
-        opts
-      )
+      {:error, error} ->
+        # I have no idea how this error handling works
+        {:error, %{error: Jason.encode!(%{captcha: [error]})}}
     end
   end
 
-  defp registration_process(params, %{registrations_open: true}, opts) do
-    create_user(params, opts)
+  defp validate_captcha(params) do
+    if Pleroma.Config.get([Pleroma.Captcha, :enabled]) do
+      Pleroma.Captcha.validate(
+        params.captcha_token,
+        params.captcha_solution,
+        params.captcha_answer_data
+      )
+    else
+      :ok
+    end
   end
 
-  defp registration_process(params, %{token: token}, opts) do
-    invite =
-      unless is_nil(token) do
-        Repo.get_by(UserInviteToken, %{token: token})
-      end
-
-    valid_invite? = invite && UserInviteToken.valid_invite?(invite)
-
-    case invite do
-      nil ->
-        {:error, "Invalid token"}
-
-      invite when valid_invite? ->
-        UserInviteToken.update_usage!(invite)
-        create_user(params, opts)
-
-      _ ->
-        {:error, "Expired token"}
+  defp create_user_with_invite(params, opts) do
+    with %{token: token} when is_binary(token) <- params,
+         %UserInviteToken{} = invite <- Repo.get_by(UserInviteToken, %{token: token}),
+         true <- UserInviteToken.valid_invite?(invite) do
+      UserInviteToken.update_usage!(invite)
+      create_user(params, opts)
+    else
+      nil -> {:error, "Invalid token"}
+      _ -> {:error, "Expired token"}
     end
   end
 
diff --git a/test/web/api_spec/account_operation_test.exs b/test/web/api_spec/account_operation_test.exs
new file mode 100644
index 000000000..4f8d04698
--- /dev/null
+++ b/test/web/api_spec/account_operation_test.exs
@@ -0,0 +1,48 @@
+# 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.AccountOperationTest do
+  use Pleroma.Web.ConnCase, async: true
+
+  alias Pleroma.Web.ApiSpec
+  alias Pleroma.Web.ApiSpec.Schemas.AccountCreateRequest
+  alias Pleroma.Web.ApiSpec.Schemas.AccountCreateResponse
+
+  import OpenApiSpex.TestAssertions
+  import Pleroma.Factory
+
+  test "AccountCreateRequest example matches schema" do
+    api_spec = ApiSpec.spec()
+    schema = AccountCreateRequest.schema()
+    assert_schema(schema.example, "AccountCreateRequest", api_spec)
+  end
+
+  test "AccountCreateResponse example matches schema" do
+    api_spec = ApiSpec.spec()
+    schema = AccountCreateResponse.schema()
+    assert_schema(schema.example, "AccountCreateResponse", api_spec)
+  end
+
+  test "AccountController produces a AccountCreateResponse", %{conn: conn} do
+    api_spec = ApiSpec.spec()
+    app_token = insert(:oauth_token, user: nil)
+
+    json =
+      conn
+      |> put_req_header("authorization", "Bearer " <> app_token.token)
+      |> put_req_header("content-type", "application/json")
+      |> post(
+        "/api/v1/accounts",
+        %{
+          username: "foo",
+          email: "bar@example.org",
+          password: "qwerty",
+          agreement: true
+        }
+      )
+      |> json_response(200)
+
+    assert_schema(json, "AccountCreateResponse", api_spec)
+  end
+end
diff --git a/test/web/mastodon_api/controllers/account_controller_test.exs b/test/web/mastodon_api/controllers/account_controller_test.exs
index a450a732c..6fe46af3c 100644
--- a/test/web/mastodon_api/controllers/account_controller_test.exs
+++ b/test/web/mastodon_api/controllers/account_controller_test.exs
@@ -830,6 +830,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
 
       conn =
         build_conn()
+        |> put_req_header("content-type", "multipart/form-data")
         |> put_req_header("authorization", "Bearer " <> token)
         |> post("/api/v1/accounts", %{
           username: "lain",
@@ -858,11 +859,12 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
       _user = insert(:user, email: "lain@example.org")
       app_token = insert(:oauth_token, user: nil)
 
-      conn =
+      res =
         conn
         |> put_req_header("authorization", "Bearer " <> app_token.token)
+        |> put_req_header("content-type", "application/json")
+        |> post("/api/v1/accounts", valid_params)
 
-      res = post(conn, "/api/v1/accounts", valid_params)
       assert json_response(res, 400) == %{"error" => "{\"email\":[\"has already been taken\"]}"}
     end
 
@@ -872,7 +874,10 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
     } do
       app_token = insert(:oauth_token, user: nil)
 
-      conn = put_req_header(conn, "authorization", "Bearer " <> app_token.token)
+      conn =
+        conn
+        |> put_req_header("authorization", "Bearer " <> app_token.token)
+        |> put_req_header("content-type", "application/json")
 
       res = post(conn, "/api/v1/accounts", valid_params)
       assert json_response(res, 200)
@@ -897,7 +902,11 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
       Pleroma.Config.put([:instance, :account_activation_required], true)
 
       app_token = insert(:oauth_token, user: nil)
-      conn = put_req_header(conn, "authorization", "Bearer " <> app_token.token)
+
+      conn =
+        conn
+        |> put_req_header("authorization", "Bearer " <> app_token.token)
+        |> put_req_header("content-type", "application/json")
 
       res =
         conn
@@ -920,6 +929,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
 
       res =
         conn
+        |> put_req_header("content-type", "application/json")
         |> Map.put(:remote_ip, {127, 0, 0, 7})
         |> post("/api/v1/accounts", Map.delete(valid_params, :email))
 
@@ -932,6 +942,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
 
       res =
         conn
+        |> put_req_header("content-type", "application/json")
         |> Map.put(:remote_ip, {127, 0, 0, 8})
         |> post("/api/v1/accounts", Map.put(valid_params, :email, ""))
 
@@ -939,9 +950,12 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
     end
 
     test "returns forbidden if token is invalid", %{conn: conn, valid_params: valid_params} do
-      conn = put_req_header(conn, "authorization", "Bearer " <> "invalid-token")
+      res =
+        conn
+        |> put_req_header("authorization", "Bearer " <> "invalid-token")
+        |> put_req_header("content-type", "multipart/form-data")
+        |> post("/api/v1/accounts", valid_params)
 
-      res = post(conn, "/api/v1/accounts", valid_params)
       assert json_response(res, 403) == %{"error" => "Invalid credentials"}
     end
   end
@@ -956,10 +970,12 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
         conn
         |> put_req_header("authorization", "Bearer " <> app_token.token)
         |> Map.put(:remote_ip, {15, 15, 15, 15})
+        |> put_req_header("content-type", "multipart/form-data")
 
       for i <- 1..2 do
         conn =
-          post(conn, "/api/v1/accounts", %{
+          conn
+          |> post("/api/v1/accounts", %{
             username: "#{i}lain",
             email: "#{i}lain@example.org",
             password: "PlzDontHackLain",
diff --git a/test/web/twitter_api/twitter_api_test.exs b/test/web/twitter_api/twitter_api_test.exs
index f6e13b661..7926a0757 100644
--- a/test/web/twitter_api/twitter_api_test.exs
+++ b/test/web/twitter_api/twitter_api_test.exs
@@ -18,11 +18,11 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do
 
   test "it registers a new user and returns the user." do
     data = %{
-      "nickname" => "lain",
-      "email" => "lain@wired.jp",
-      "fullname" => "lain iwakura",
-      "password" => "bear",
-      "confirm" => "bear"
+      :nickname => "lain",
+      :email => "lain@wired.jp",
+      :fullname => "lain iwakura",
+      :password => "bear",
+      :confirm => "bear"
     }
 
     {:ok, user} = TwitterAPI.register_user(data)
@@ -35,12 +35,12 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do
 
   test "it registers a new user with empty string in bio and returns the user." do
     data = %{
-      "nickname" => "lain",
-      "email" => "lain@wired.jp",
-      "fullname" => "lain iwakura",
-      "bio" => "",
-      "password" => "bear",
-      "confirm" => "bear"
+      :nickname => "lain",
+      :email => "lain@wired.jp",
+      :fullname => "lain iwakura",
+      :bio => "",
+      :password => "bear",
+      :confirm => "bear"
     }
 
     {:ok, user} = TwitterAPI.register_user(data)
@@ -60,12 +60,12 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do
     end
 
     data = %{
-      "nickname" => "lain",
-      "email" => "lain@wired.jp",
-      "fullname" => "lain iwakura",
-      "bio" => "",
-      "password" => "bear",
-      "confirm" => "bear"
+      :nickname => "lain",
+      :email => "lain@wired.jp",
+      :fullname => "lain iwakura",
+      :bio => "",
+      :password => "bear",
+      :confirm => "bear"
     }
 
     {:ok, user} = TwitterAPI.register_user(data)
@@ -87,23 +87,23 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do
 
   test "it registers a new user and parses mentions in the bio" do
     data1 = %{
-      "nickname" => "john",
-      "email" => "john@gmail.com",
-      "fullname" => "John Doe",
-      "bio" => "test",
-      "password" => "bear",
-      "confirm" => "bear"
+      :nickname => "john",
+      :email => "john@gmail.com",
+      :fullname => "John Doe",
+      :bio => "test",
+      :password => "bear",
+      :confirm => "bear"
     }
 
     {:ok, user1} = TwitterAPI.register_user(data1)
 
     data2 = %{
-      "nickname" => "lain",
-      "email" => "lain@wired.jp",
-      "fullname" => "lain iwakura",
-      "bio" => "@john test",
-      "password" => "bear",
-      "confirm" => "bear"
+      :nickname => "lain",
+      :email => "lain@wired.jp",
+      :fullname => "lain iwakura",
+      :bio => "@john test",
+      :password => "bear",
+      :confirm => "bear"
     }
 
     {:ok, user2} = TwitterAPI.register_user(data2)
@@ -123,13 +123,13 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do
       {:ok, invite} = UserInviteToken.create_invite()
 
       data = %{
-        "nickname" => "vinny",
-        "email" => "pasta@pizza.vs",
-        "fullname" => "Vinny Vinesauce",
-        "bio" => "streamer",
-        "password" => "hiptofbees",
-        "confirm" => "hiptofbees",
-        "token" => invite.token
+        :nickname => "vinny",
+        :email => "pasta@pizza.vs",
+        :fullname => "Vinny Vinesauce",
+        :bio => "streamer",
+        :password => "hiptofbees",
+        :confirm => "hiptofbees",
+        :token => invite.token
       }
 
       {:ok, user} = TwitterAPI.register_user(data)
@@ -145,13 +145,13 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do
 
     test "returns error on invalid token" do
       data = %{
-        "nickname" => "GrimReaper",
-        "email" => "death@reapers.afterlife",
-        "fullname" => "Reaper Grim",
-        "bio" => "Your time has come",
-        "password" => "scythe",
-        "confirm" => "scythe",
-        "token" => "DudeLetMeInImAFairy"
+        :nickname => "GrimReaper",
+        :email => "death@reapers.afterlife",
+        :fullname => "Reaper Grim",
+        :bio => "Your time has come",
+        :password => "scythe",
+        :confirm => "scythe",
+        :token => "DudeLetMeInImAFairy"
       }
 
       {:error, msg} = TwitterAPI.register_user(data)
@@ -165,13 +165,13 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do
       UserInviteToken.update_invite!(invite, used: true)
 
       data = %{
-        "nickname" => "GrimReaper",
-        "email" => "death@reapers.afterlife",
-        "fullname" => "Reaper Grim",
-        "bio" => "Your time has come",
-        "password" => "scythe",
-        "confirm" => "scythe",
-        "token" => invite.token
+        :nickname => "GrimReaper",
+        :email => "death@reapers.afterlife",
+        :fullname => "Reaper Grim",
+        :bio => "Your time has come",
+        :password => "scythe",
+        :confirm => "scythe",
+        :token => invite.token
       }
 
       {:error, msg} = TwitterAPI.register_user(data)
@@ -186,16 +186,16 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do
 
     setup do
       data = %{
-        "nickname" => "vinny",
-        "email" => "pasta@pizza.vs",
-        "fullname" => "Vinny Vinesauce",
-        "bio" => "streamer",
-        "password" => "hiptofbees",
-        "confirm" => "hiptofbees"
+        :nickname => "vinny",
+        :email => "pasta@pizza.vs",
+        :fullname => "Vinny Vinesauce",
+        :bio => "streamer",
+        :password => "hiptofbees",
+        :confirm => "hiptofbees"
       }
 
       check_fn = fn invite ->
-        data = Map.put(data, "token", invite.token)
+        data = Map.put(data, :token, invite.token)
         {:ok, user} = TwitterAPI.register_user(data)
         fetched_user = User.get_cached_by_nickname("vinny")
 
@@ -250,13 +250,13 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do
       UserInviteToken.update_invite!(invite, uses: 99)
 
       data = %{
-        "nickname" => "vinny",
-        "email" => "pasta@pizza.vs",
-        "fullname" => "Vinny Vinesauce",
-        "bio" => "streamer",
-        "password" => "hiptofbees",
-        "confirm" => "hiptofbees",
-        "token" => invite.token
+        :nickname => "vinny",
+        :email => "pasta@pizza.vs",
+        :fullname => "Vinny Vinesauce",
+        :bio => "streamer",
+        :password => "hiptofbees",
+        :confirm => "hiptofbees",
+        :token => invite.token
       }
 
       {:ok, user} = TwitterAPI.register_user(data)
@@ -269,13 +269,13 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do
                AccountView.render("show.json", %{user: fetched_user})
 
       data = %{
-        "nickname" => "GrimReaper",
-        "email" => "death@reapers.afterlife",
-        "fullname" => "Reaper Grim",
-        "bio" => "Your time has come",
-        "password" => "scythe",
-        "confirm" => "scythe",
-        "token" => invite.token
+        :nickname => "GrimReaper",
+        :email => "death@reapers.afterlife",
+        :fullname => "Reaper Grim",
+        :bio => "Your time has come",
+        :password => "scythe",
+        :confirm => "scythe",
+        :token => invite.token
       }
 
       {:error, msg} = TwitterAPI.register_user(data)
@@ -292,13 +292,13 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do
       {:ok, invite} = UserInviteToken.create_invite(%{expires_at: Date.utc_today(), max_use: 100})
 
       data = %{
-        "nickname" => "vinny",
-        "email" => "pasta@pizza.vs",
-        "fullname" => "Vinny Vinesauce",
-        "bio" => "streamer",
-        "password" => "hiptofbees",
-        "confirm" => "hiptofbees",
-        "token" => invite.token
+        :nickname => "vinny",
+        :email => "pasta@pizza.vs",
+        :fullname => "Vinny Vinesauce",
+        :bio => "streamer",
+        :password => "hiptofbees",
+        :confirm => "hiptofbees",
+        :token => invite.token
       }
 
       {:ok, user} = TwitterAPI.register_user(data)
@@ -317,13 +317,13 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do
       UserInviteToken.update_invite!(invite, uses: 99)
 
       data = %{
-        "nickname" => "vinny",
-        "email" => "pasta@pizza.vs",
-        "fullname" => "Vinny Vinesauce",
-        "bio" => "streamer",
-        "password" => "hiptofbees",
-        "confirm" => "hiptofbees",
-        "token" => invite.token
+        :nickname => "vinny",
+        :email => "pasta@pizza.vs",
+        :fullname => "Vinny Vinesauce",
+        :bio => "streamer",
+        :password => "hiptofbees",
+        :confirm => "hiptofbees",
+        :token => invite.token
       }
 
       {:ok, user} = TwitterAPI.register_user(data)
@@ -335,13 +335,13 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do
                AccountView.render("show.json", %{user: fetched_user})
 
       data = %{
-        "nickname" => "GrimReaper",
-        "email" => "death@reapers.afterlife",
-        "fullname" => "Reaper Grim",
-        "bio" => "Your time has come",
-        "password" => "scythe",
-        "confirm" => "scythe",
-        "token" => invite.token
+        :nickname => "GrimReaper",
+        :email => "death@reapers.afterlife",
+        :fullname => "Reaper Grim",
+        :bio => "Your time has come",
+        :password => "scythe",
+        :confirm => "scythe",
+        :token => invite.token
       }
 
       {:error, msg} = TwitterAPI.register_user(data)
@@ -355,13 +355,13 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do
         UserInviteToken.create_invite(%{expires_at: Date.add(Date.utc_today(), -1), max_use: 100})
 
       data = %{
-        "nickname" => "GrimReaper",
-        "email" => "death@reapers.afterlife",
-        "fullname" => "Reaper Grim",
-        "bio" => "Your time has come",
-        "password" => "scythe",
-        "confirm" => "scythe",
-        "token" => invite.token
+        :nickname => "GrimReaper",
+        :email => "death@reapers.afterlife",
+        :fullname => "Reaper Grim",
+        :bio => "Your time has come",
+        :password => "scythe",
+        :confirm => "scythe",
+        :token => invite.token
       }
 
       {:error, msg} = TwitterAPI.register_user(data)
@@ -377,13 +377,13 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do
       UserInviteToken.update_invite!(invite, uses: 100)
 
       data = %{
-        "nickname" => "GrimReaper",
-        "email" => "death@reapers.afterlife",
-        "fullname" => "Reaper Grim",
-        "bio" => "Your time has come",
-        "password" => "scythe",
-        "confirm" => "scythe",
-        "token" => invite.token
+        :nickname => "GrimReaper",
+        :email => "death@reapers.afterlife",
+        :fullname => "Reaper Grim",
+        :bio => "Your time has come",
+        :password => "scythe",
+        :confirm => "scythe",
+        :token => invite.token
       }
 
       {:error, msg} = TwitterAPI.register_user(data)
@@ -395,11 +395,11 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do
 
   test "it returns the error on registration problems" do
     data = %{
-      "nickname" => "lain",
-      "email" => "lain@wired.jp",
-      "fullname" => "lain iwakura",
-      "bio" => "close the world.",
-      "password" => "bear"
+      :nickname => "lain",
+      :email => "lain@wired.jp",
+      :fullname => "lain iwakura",
+      :bio => "close the world.",
+      :password => "bear"
     }
 
     {:error, error_object} = TwitterAPI.register_user(data)

From f80116125f928de36c93627bbdf5f6578396f53b Mon Sep 17 00:00:00 2001
From: Egor Kislitsyn <egor@kislitsyn.com>
Date: Mon, 6 Apr 2020 00:15:37 +0400
Subject: [PATCH 02/40] Add spec for AccountController.verify_credentials

---
 lib/pleroma/web/api_spec.ex                   |   2 +-
 .../api_spec/operations/account_operation.ex  |  14 +-
 .../web/api_spec/operations/app_operation.ex  |   6 +-
 lib/pleroma/web/api_spec/render_error.ex      |   2 +-
 lib/pleroma/web/api_spec/schemas/account.ex   | 181 ++++++++++++++++++
 .../web/api_spec/schemas/account_emoji.ex     |  31 +++
 .../web/api_spec/schemas/account_field.ex     |  28 +++
 test/web/api_spec/account_operation_test.exs  |   7 +
 8 files changed, 262 insertions(+), 9 deletions(-)
 create mode 100644 lib/pleroma/web/api_spec/schemas/account.ex
 create mode 100644 lib/pleroma/web/api_spec/schemas/account_emoji.ex
 create mode 100644 lib/pleroma/web/api_spec/schemas/account_field.ex

diff --git a/lib/pleroma/web/api_spec.ex b/lib/pleroma/web/api_spec.ex
index 41e48a085..c85fe30d1 100644
--- a/lib/pleroma/web/api_spec.ex
+++ b/lib/pleroma/web/api_spec.ex
@@ -31,7 +31,7 @@ defmodule Pleroma.Web.ApiSpec do
               password: %OpenApiSpex.OAuthFlow{
                 authorizationUrl: "/oauth/authorize",
                 tokenUrl: "/oauth/token",
-                scopes: %{"read" => "read"}
+                scopes: %{"read" => "read", "write" => "write"}
               }
             }
           }
diff --git a/lib/pleroma/web/api_spec/operations/account_operation.ex b/lib/pleroma/web/api_spec/operations/account_operation.ex
index 9085f1af1..3d2270c29 100644
--- a/lib/pleroma/web/api_spec/operations/account_operation.ex
+++ b/lib/pleroma/web/api_spec/operations/account_operation.ex
@@ -4,9 +4,10 @@
 
 defmodule Pleroma.Web.ApiSpec.AccountOperation do
   alias OpenApiSpex.Operation
+  alias Pleroma.Web.ApiSpec.Helpers
+  alias Pleroma.Web.ApiSpec.Schemas.Account
   alias Pleroma.Web.ApiSpec.Schemas.AccountCreateRequest
   alias Pleroma.Web.ApiSpec.Schemas.AccountCreateResponse
-  alias Pleroma.Web.ApiSpec.Helpers
 
   @spec open_api_operation(atom) :: Operation.t()
   def open_api_operation(action) do
@@ -30,7 +31,16 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
   end
 
   def verify_credentials_operation do
-    :ok
+    %Operation{
+      tags: ["accounts"],
+      description: "Test to make sure that the user token works.",
+      summary: "Verify account credentials",
+      operationId: "AccountController.verify_credentials",
+      security: [%{"oAuth" => ["read:accounts"]}],
+      responses: %{
+        200 => Operation.response("Account", "application/json", Account)
+      }
+    }
   end
 
   def update_credentials_operation do
diff --git a/lib/pleroma/web/api_spec/operations/app_operation.ex b/lib/pleroma/web/api_spec/operations/app_operation.ex
index 26d8dbd42..935215c64 100644
--- a/lib/pleroma/web/api_spec/operations/app_operation.ex
+++ b/lib/pleroma/web/api_spec/operations/app_operation.ex
@@ -51,11 +51,7 @@ defmodule Pleroma.Web.ApiSpec.AppOperation do
       summary: "Verify your app works",
       description: "Confirm that the app's OAuth2 credentials work.",
       operationId: "AppController.verify_credentials",
-      security: [
-        %{
-          "oAuth" => ["read"]
-        }
-      ],
+      security: [%{"oAuth" => ["read"]}],
       responses: %{
         200 =>
           Operation.response("App", "application/json", %Schema{
diff --git a/lib/pleroma/web/api_spec/render_error.ex b/lib/pleroma/web/api_spec/render_error.ex
index e063d115b..9184c43b6 100644
--- a/lib/pleroma/web/api_spec/render_error.ex
+++ b/lib/pleroma/web/api_spec/render_error.ex
@@ -5,8 +5,8 @@
 defmodule Pleroma.Web.ApiSpec.RenderError do
   @behaviour Plug
 
-  alias Plug.Conn
   alias OpenApiSpex.Plug.JsonRenderError
+  alias Plug.Conn
 
   @impl Plug
   def init(opts), do: opts
diff --git a/lib/pleroma/web/api_spec/schemas/account.ex b/lib/pleroma/web/api_spec/schemas/account.ex
new file mode 100644
index 000000000..59c4ac4a4
--- /dev/null
+++ b/lib/pleroma/web/api_spec/schemas/account.ex
@@ -0,0 +1,181 @@
+# 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.Account do
+  alias OpenApiSpex.Schema
+  alias Pleroma.Web.ApiSpec.Schemas.AccountEmoji
+  alias Pleroma.Web.ApiSpec.Schemas.AccountField
+
+  require OpenApiSpex
+
+  OpenApiSpex.schema(%{
+    title: "Account",
+    description: "Response schema for an account",
+    type: :object,
+    properties: %{
+      acct: %Schema{type: :string},
+      avatar_static: %Schema{type: :string},
+      avatar: %Schema{type: :string},
+      bot: %Schema{type: :boolean},
+      created_at: %Schema{type: :string, format: "date-time"},
+      display_name: %Schema{type: :string},
+      emojis: %Schema{type: :array, items: AccountEmoji},
+      fields: %Schema{type: :array, items: AccountField},
+      follow_requests_count: %Schema{type: :integer},
+      followers_count: %Schema{type: :integer},
+      following_count: %Schema{type: :integer},
+      header_static: %Schema{type: :string},
+      header: %Schema{type: :string},
+      id: %Schema{type: :string},
+      locked: %Schema{type: :boolean},
+      note: %Schema{type: :string},
+      statuses_count: %Schema{type: :integer},
+      url: %Schema{type: :string},
+      username: %Schema{type: :string},
+      pleroma: %Schema{
+        type: :object,
+        properties: %{
+          allow_following_move: %Schema{type: :boolean},
+          background_image: %Schema{type: :boolean, nullable: true},
+          chat_token: %Schema{type: :string},
+          confirmation_pending: %Schema{type: :boolean},
+          hide_favorites: %Schema{type: :boolean},
+          hide_followers_count: %Schema{type: :boolean},
+          hide_followers: %Schema{type: :boolean},
+          hide_follows_count: %Schema{type: :boolean},
+          hide_follows: %Schema{type: :boolean},
+          is_admin: %Schema{type: :boolean},
+          is_moderator: %Schema{type: :boolean},
+          skip_thread_containment: %Schema{type: :boolean},
+          tags: %Schema{type: :array, items: %Schema{type: :string}},
+          unread_conversation_count: %Schema{type: :integer},
+          notification_settings: %Schema{
+            type: :object,
+            properties: %{
+              followers: %Schema{type: :boolean},
+              follows: %Schema{type: :boolean},
+              non_followers: %Schema{type: :boolean},
+              non_follows: %Schema{type: :boolean},
+              privacy_option: %Schema{type: :boolean}
+            }
+          },
+          relationship: %Schema{
+            type: :object,
+            properties: %{
+              blocked_by: %Schema{type: :boolean},
+              blocking: %Schema{type: :boolean},
+              domain_blocking: %Schema{type: :boolean},
+              endorsed: %Schema{type: :boolean},
+              followed_by: %Schema{type: :boolean},
+              following: %Schema{type: :boolean},
+              id: %Schema{type: :string},
+              muting: %Schema{type: :boolean},
+              muting_notifications: %Schema{type: :boolean},
+              requested: %Schema{type: :boolean},
+              showing_reblogs: %Schema{type: :boolean},
+              subscribing: %Schema{type: :boolean}
+            }
+          },
+          settings_store: %Schema{
+            type: :object
+          }
+        }
+      },
+      source: %Schema{
+        type: :object,
+        properties: %{
+          fields: %Schema{type: :array, items: AccountField},
+          note: %Schema{type: :string},
+          privacy: %Schema{type: :string},
+          sensitive: %Schema{type: :boolean},
+          pleroma: %Schema{
+            type: :object,
+            properties: %{
+              actor_type: %Schema{type: :string},
+              discoverable: %Schema{type: :boolean},
+              no_rich_text: %Schema{type: :boolean},
+              show_role: %Schema{type: :boolean}
+            }
+          }
+        }
+      }
+    },
+    example: %{
+      "JSON" => %{
+        "acct" => "foobar",
+        "avatar" => "https://mypleroma.com/images/avi.png",
+        "avatar_static" => "https://mypleroma.com/images/avi.png",
+        "bot" => false,
+        "created_at" => "2020-03-24T13:05:58.000Z",
+        "display_name" => "foobar",
+        "emojis" => [],
+        "fields" => [],
+        "follow_requests_count" => 0,
+        "followers_count" => 0,
+        "following_count" => 1,
+        "header" => "https://mypleroma.com/images/banner.png",
+        "header_static" => "https://mypleroma.com/images/banner.png",
+        "id" => "9tKi3esbG7OQgZ2920",
+        "locked" => false,
+        "note" => "cofe",
+        "pleroma" => %{
+          "allow_following_move" => true,
+          "background_image" => nil,
+          "confirmation_pending" => true,
+          "hide_favorites" => true,
+          "hide_followers" => false,
+          "hide_followers_count" => false,
+          "hide_follows" => false,
+          "hide_follows_count" => false,
+          "is_admin" => false,
+          "is_moderator" => false,
+          "skip_thread_containment" => false,
+          "chat_token" =>
+            "SFMyNTY.g3QAAAACZAAEZGF0YW0AAAASOXRLaTNlc2JHN09RZ1oyOTIwZAAGc2lnbmVkbgYARNplS3EB.Mb_Iaqew2bN1I1o79B_iP7encmVCpTKC4OtHZRxdjKc",
+          "unread_conversation_count" => 0,
+          "tags" => [],
+          "notification_settings" => %{
+            "followers" => true,
+            "follows" => true,
+            "non_followers" => true,
+            "non_follows" => true,
+            "privacy_option" => false
+          },
+          "relationship" => %{
+            "blocked_by" => false,
+            "blocking" => false,
+            "domain_blocking" => false,
+            "endorsed" => false,
+            "followed_by" => false,
+            "following" => false,
+            "id" => "9tKi3esbG7OQgZ2920",
+            "muting" => false,
+            "muting_notifications" => false,
+            "requested" => false,
+            "showing_reblogs" => true,
+            "subscribing" => false
+          },
+          "settings_store" => %{
+            "pleroma-fe" => %{}
+          }
+        },
+        "source" => %{
+          "fields" => [],
+          "note" => "foobar",
+          "pleroma" => %{
+            "actor_type" => "Person",
+            "discoverable" => false,
+            "no_rich_text" => false,
+            "show_role" => true
+          },
+          "privacy" => "public",
+          "sensitive" => false
+        },
+        "statuses_count" => 0,
+        "url" => "https://mypleroma.com/users/foobar",
+        "username" => "foobar"
+      }
+    }
+  })
+end
diff --git a/lib/pleroma/web/api_spec/schemas/account_emoji.ex b/lib/pleroma/web/api_spec/schemas/account_emoji.ex
new file mode 100644
index 000000000..403b13b15
--- /dev/null
+++ b/lib/pleroma/web/api_spec/schemas/account_emoji.ex
@@ -0,0 +1,31 @@
+# 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.AccountEmoji do
+  alias OpenApiSpex.Schema
+
+  require OpenApiSpex
+
+  OpenApiSpex.schema(%{
+    title: "AccountEmoji",
+    description: "Response schema for account custom fields",
+    type: :object,
+    properties: %{
+      shortcode: %Schema{type: :string},
+      url: %Schema{type: :string},
+      static_url: %Schema{type: :string},
+      visible_in_picker: %Schema{type: :boolean}
+    },
+    example: %{
+      "JSON" => %{
+        "shortcode" => "fatyoshi",
+        "url" =>
+          "https://files.mastodon.social/custom_emojis/images/000/023/920/original/e57ecb623faa0dc9.png",
+        "static_url" =>
+          "https://files.mastodon.social/custom_emojis/images/000/023/920/static/e57ecb623faa0dc9.png",
+        "visible_in_picker" => true
+      }
+    }
+  })
+end
diff --git a/lib/pleroma/web/api_spec/schemas/account_field.ex b/lib/pleroma/web/api_spec/schemas/account_field.ex
new file mode 100644
index 000000000..8906d812d
--- /dev/null
+++ b/lib/pleroma/web/api_spec/schemas/account_field.ex
@@ -0,0 +1,28 @@
+# 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.AccountField do
+  alias OpenApiSpex.Schema
+
+  require OpenApiSpex
+
+  OpenApiSpex.schema(%{
+    title: "AccountField",
+    description: "Response schema for account custom fields",
+    type: :object,
+    properties: %{
+      name: %Schema{type: :string},
+      value: %Schema{type: :string},
+      verified_at: %Schema{type: :string, format: "date-time", nullable: true}
+    },
+    example: %{
+      "JSON" => %{
+        "name" => "Website",
+        "value" =>
+          "<a href=\"https://pleroma.com\" rel=\"me nofollow noopener noreferrer\" target=\"_blank\"><span class=\"invisible\">https://</span><span class=\"\">pleroma.com</span><span class=\"invisible\"></span></a>",
+        "verified_at" => "2019-08-29T04:14:55.571+00:00"
+      }
+    }
+  })
+end
diff --git a/test/web/api_spec/account_operation_test.exs b/test/web/api_spec/account_operation_test.exs
index 4f8d04698..37501b8cc 100644
--- a/test/web/api_spec/account_operation_test.exs
+++ b/test/web/api_spec/account_operation_test.exs
@@ -6,12 +6,19 @@ defmodule Pleroma.Web.ApiSpec.AccountOperationTest do
   use Pleroma.Web.ConnCase, async: true
 
   alias Pleroma.Web.ApiSpec
+  alias Pleroma.Web.ApiSpec.Schemas.Account
   alias Pleroma.Web.ApiSpec.Schemas.AccountCreateRequest
   alias Pleroma.Web.ApiSpec.Schemas.AccountCreateResponse
 
   import OpenApiSpex.TestAssertions
   import Pleroma.Factory
 
+  test "Account example matches schema" do
+    api_spec = ApiSpec.spec()
+    schema = Account.schema()
+    assert_schema(schema.example, "Account", api_spec)
+  end
+
   test "AccountCreateRequest example matches schema" do
     api_spec = ApiSpec.spec()
     schema = AccountCreateRequest.schema()

From 260cbddc943e53a85762e56852de65d2b900cc04 Mon Sep 17 00:00:00 2001
From: Egor Kislitsyn <egor@kislitsyn.com>
Date: Tue, 7 Apr 2020 14:53:12 +0400
Subject: [PATCH 03/40] Add spec for AccountController.update_credentials

---
 lib/pleroma/web/api_spec/helpers.ex           |   2 +-
 .../api_spec/operations/account_operation.ex  |  14 +-
 .../schemas/account_field_attribute.ex        |  26 ++++
 .../account_update_credentials_request.ex     | 123 ++++++++++++++++++
 .../controllers/account_controller.ex         |  41 ++++--
 test/support/conn_case.ex                     |   5 +
 test/web/api_spec/account_operation_test.exs  |  32 +++++
 .../update_credentials_test.exs               |   2 +
 8 files changed, 229 insertions(+), 16 deletions(-)
 create mode 100644 lib/pleroma/web/api_spec/schemas/account_field_attribute.ex
 create mode 100644 lib/pleroma/web/api_spec/schemas/account_update_credentials_request.ex

diff --git a/lib/pleroma/web/api_spec/helpers.ex b/lib/pleroma/web/api_spec/helpers.ex
index 35cf4c0d8..7348dcbee 100644
--- a/lib/pleroma/web/api_spec/helpers.ex
+++ b/lib/pleroma/web/api_spec/helpers.ex
@@ -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
diff --git a/lib/pleroma/web/api_spec/operations/account_operation.ex b/lib/pleroma/web/api_spec/operations/account_operation.ex
index 3d2270c29..d7b56cc2b 100644
--- a/lib/pleroma/web/api_spec/operations/account_operation.ex
+++ b/lib/pleroma/web/api_spec/operations/account_operation.ex
@@ -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
index 000000000..fbbdf95f5
--- /dev/null
+++ b/lib/pleroma/web/api_spec/schemas/account_field_attribute.ex
@@ -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
index 000000000..a50bce5ed
--- /dev/null
+++ b/lib/pleroma/web/api_spec/schemas/account_update_credentials_request.ex
@@ -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
diff --git a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
index eb082daf8..9c986b3b2 100644
--- a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
@@ -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
 
diff --git a/test/support/conn_case.ex b/test/support/conn_case.ex
index 064874201..36ce372c2 100644
--- a/test/support/conn_case.ex
+++ b/test/support/conn_case.ex
@@ -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)
diff --git a/test/web/api_spec/account_operation_test.exs b/test/web/api_spec/account_operation_test.exs
index 37501b8cc..a54059074 100644
--- a/test/web/api_spec/account_operation_test.exs
+++ b/test/web/api_spec/account_operation_test.exs
@@ -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
diff --git a/test/web/mastodon_api/controllers/account_controller/update_credentials_test.exs b/test/web/mastodon_api/controllers/account_controller/update_credentials_test.exs
index 2d256f63c..0e890a980 100644
--- a/test/web/mastodon_api/controllers/account_controller/update_credentials_test.exs
+++ b/test/web/mastodon_api/controllers/account_controller/update_credentials_test.exs
@@ -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", %{})
 

From ab400b2ddb205271b0a2680c45db18844f59a27d Mon Sep 17 00:00:00 2001
From: Egor Kislitsyn <egor@kislitsyn.com>
Date: Tue, 7 Apr 2020 16:18:23 +0400
Subject: [PATCH 04/40] Add specs for ActorType and VisibilityScope

---
 lib/pleroma/web/api_spec/schemas/account.ex        |  6 ++++--
 .../schemas/account_update_credentials_request.ex  |  9 ++++-----
 lib/pleroma/web/api_spec/schemas/actor_type.ex     | 13 +++++++++++++
 .../web/api_spec/schemas/visibility_scope.ex       | 14 ++++++++++++++
 .../account_controller/update_credentials_test.exs |  4 ++--
 5 files changed, 37 insertions(+), 9 deletions(-)
 create mode 100644 lib/pleroma/web/api_spec/schemas/actor_type.ex
 create mode 100644 lib/pleroma/web/api_spec/schemas/visibility_scope.ex

diff --git a/lib/pleroma/web/api_spec/schemas/account.ex b/lib/pleroma/web/api_spec/schemas/account.ex
index 59c4ac4a4..beb093182 100644
--- a/lib/pleroma/web/api_spec/schemas/account.ex
+++ b/lib/pleroma/web/api_spec/schemas/account.ex
@@ -6,6 +6,8 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Account do
   alias OpenApiSpex.Schema
   alias Pleroma.Web.ApiSpec.Schemas.AccountEmoji
   alias Pleroma.Web.ApiSpec.Schemas.AccountField
+  alias Pleroma.Web.ApiSpec.Schemas.ActorType
+  alias Pleroma.Web.ApiSpec.Schemas.VisibilityScope
 
   require OpenApiSpex
 
@@ -87,12 +89,12 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Account do
         properties: %{
           fields: %Schema{type: :array, items: AccountField},
           note: %Schema{type: :string},
-          privacy: %Schema{type: :string},
+          privacy: VisibilityScope,
           sensitive: %Schema{type: :boolean},
           pleroma: %Schema{
             type: :object,
             properties: %{
-              actor_type: %Schema{type: :string},
+              actor_type: ActorType,
               discoverable: %Schema{type: :boolean},
               no_rich_text: %Schema{type: :boolean},
               show_role: %Schema{type: :boolean}
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
index a50bce5ed..6ab48193e 100644
--- a/lib/pleroma/web/api_spec/schemas/account_update_credentials_request.ex
+++ b/lib/pleroma/web/api_spec/schemas/account_update_credentials_request.ex
@@ -5,6 +5,8 @@
 defmodule Pleroma.Web.ApiSpec.Schemas.AccountUpdateCredentialsRequest do
   alias OpenApiSpex.Schema
   alias Pleroma.Web.ApiSpec.Schemas.AccountAttributeField
+  alias Pleroma.Web.ApiSpec.Schemas.ActorType
+  alias Pleroma.Web.ApiSpec.Schemas.VisibilityScope
   require OpenApiSpex
 
   OpenApiSpex.schema(%{
@@ -73,10 +75,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.AccountUpdateCredentialsRequest do
         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"
-      },
+      default_scope: VisibilityScope,
       pleroma_settings_store: %Schema{
         type: :object,
         description: "Opaque user settings to be saved on the backend."
@@ -98,7 +97,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.AccountUpdateCredentialsRequest do
         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."}
+      actor_type: ActorType
     },
     example: %{
       bot: false,
diff --git a/lib/pleroma/web/api_spec/schemas/actor_type.ex b/lib/pleroma/web/api_spec/schemas/actor_type.ex
new file mode 100644
index 000000000..ac9b46678
--- /dev/null
+++ b/lib/pleroma/web/api_spec/schemas/actor_type.ex
@@ -0,0 +1,13 @@
+# 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.ActorType do
+  require OpenApiSpex
+
+  OpenApiSpex.schema(%{
+    title: "ActorType",
+    type: :string,
+    enum: ["Application", "Group", "Organization", "Person", "Service"]
+  })
+end
diff --git a/lib/pleroma/web/api_spec/schemas/visibility_scope.ex b/lib/pleroma/web/api_spec/schemas/visibility_scope.ex
new file mode 100644
index 000000000..8c81a4d73
--- /dev/null
+++ b/lib/pleroma/web/api_spec/schemas/visibility_scope.ex
@@ -0,0 +1,14 @@
+# 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.VisibilityScope do
+  require OpenApiSpex
+
+  OpenApiSpex.schema(%{
+    title: "VisibilityScope",
+    description: "Status visibility",
+    type: :string,
+    enum: ["public", "unlisted", "private", "direct"]
+  })
+end
diff --git a/test/web/mastodon_api/controllers/account_controller/update_credentials_test.exs b/test/web/mastodon_api/controllers/account_controller/update_credentials_test.exs
index 0e890a980..a3356c12f 100644
--- a/test/web/mastodon_api/controllers/account_controller/update_credentials_test.exs
+++ b/test/web/mastodon_api/controllers/account_controller/update_credentials_test.exs
@@ -106,10 +106,10 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController.UpdateCredentialsTest do
     end
 
     test "updates the user's default scope", %{conn: conn} do
-      conn = patch(conn, "/api/v1/accounts/update_credentials", %{default_scope: "cofe"})
+      conn = patch(conn, "/api/v1/accounts/update_credentials", %{default_scope: "unlisted"})
 
       assert user_data = json_response(conn, 200)
-      assert user_data["source"]["privacy"] == "cofe"
+      assert user_data["source"]["privacy"] == "unlisted"
     end
 
     test "updates the user's hide_followers status", %{conn: conn} do

From d7d6a83233f24b80005b4f49a8697535620e4b83 Mon Sep 17 00:00:00 2001
From: Egor Kislitsyn <egor@kislitsyn.com>
Date: Tue, 7 Apr 2020 18:29:05 +0400
Subject: [PATCH 05/40] Add spec for AccountController.relationships

---
 .../api_spec/operations/account_operation.ex  | 24 +++++++-
 .../schemas/account_relationship_response.ex  | 43 +++++++++++++++
 .../schemas/account_relationships_response.ex | 55 +++++++++++++++++++
 .../controllers/account_controller.ex         |  4 +-
 test/web/api_spec/account_operation_test.exs  | 24 ++++++++
 .../controllers/account_controller_test.exs   | 14 +++--
 6 files changed, 156 insertions(+), 8 deletions(-)
 create mode 100644 lib/pleroma/web/api_spec/schemas/account_relationship_response.ex
 create mode 100644 lib/pleroma/web/api_spec/schemas/account_relationships_response.ex

diff --git a/lib/pleroma/web/api_spec/operations/account_operation.ex b/lib/pleroma/web/api_spec/operations/account_operation.ex
index d7b56cc2b..352f66e9d 100644
--- a/lib/pleroma/web/api_spec/operations/account_operation.ex
+++ b/lib/pleroma/web/api_spec/operations/account_operation.ex
@@ -4,10 +4,12 @@
 
 defmodule Pleroma.Web.ApiSpec.AccountOperation do
   alias OpenApiSpex.Operation
+  alias OpenApiSpex.Schema
   alias Pleroma.Web.ApiSpec.Helpers
   alias Pleroma.Web.ApiSpec.Schemas.Account
   alias Pleroma.Web.ApiSpec.Schemas.AccountCreateRequest
   alias Pleroma.Web.ApiSpec.Schemas.AccountCreateResponse
+  alias Pleroma.Web.ApiSpec.Schemas.AccountRelationshipsResponse
   alias Pleroma.Web.ApiSpec.Schemas.AccountUpdateCredentialsRequest
 
   @spec open_api_operation(atom) :: Operation.t()
@@ -60,7 +62,27 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
   end
 
   def relationships_operation do
-    :ok
+    %Operation{
+      tags: ["accounts"],
+      summary: "Check relationships to other accounts",
+      operationId: "AccountController.relationships",
+      description: "Find out whether a given account is followed, blocked, muted, etc.",
+      security: [%{"oAuth" => ["read:follows"]}],
+      parameters: [
+        Operation.parameter(
+          :id,
+          :query,
+          %Schema{
+            oneOf: [%Schema{type: :array, items: %Schema{type: :string}}, %Schema{type: :string}]
+          },
+          "Account IDs",
+          example: "123"
+        )
+      ],
+      responses: %{
+        200 => Operation.response("Account", "application/json", AccountRelationshipsResponse)
+      }
+    }
   end
 
   def show_operation do
diff --git a/lib/pleroma/web/api_spec/schemas/account_relationship_response.ex b/lib/pleroma/web/api_spec/schemas/account_relationship_response.ex
new file mode 100644
index 000000000..9974b946b
--- /dev/null
+++ b/lib/pleroma/web/api_spec/schemas/account_relationship_response.ex
@@ -0,0 +1,43 @@
+# 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.AccountRelationshipResponse do
+  alias OpenApiSpex.Schema
+
+  require OpenApiSpex
+
+  OpenApiSpex.schema(%{
+    title: "AccountRelationshipResponse",
+    description: "Response schema for an account relationship",
+    type: :object,
+    properties: %{
+      id: %Schema{type: :string},
+      following: %Schema{type: :boolean},
+      showing_reblogs: %Schema{type: :boolean},
+      followed_by: %Schema{type: :boolean},
+      blocking: %Schema{type: :boolean},
+      blocked_by: %Schema{type: :boolean},
+      muting: %Schema{type: :boolean},
+      muting_notifications: %Schema{type: :boolean},
+      requested: %Schema{type: :boolean},
+      domain_blocking: %Schema{type: :boolean},
+      endorsed: %Schema{type: :boolean}
+    },
+    example: %{
+      "JSON" => %{
+        "id" => "1",
+        "following" => true,
+        "showing_reblogs" => true,
+        "followed_by" => true,
+        "blocking" => false,
+        "blocked_by" => false,
+        "muting" => false,
+        "muting_notifications" => false,
+        "requested" => false,
+        "domain_blocking" => false,
+        "endorsed" => false
+      }
+    }
+  })
+end
diff --git a/lib/pleroma/web/api_spec/schemas/account_relationships_response.ex b/lib/pleroma/web/api_spec/schemas/account_relationships_response.ex
new file mode 100644
index 000000000..2ca632310
--- /dev/null
+++ b/lib/pleroma/web/api_spec/schemas/account_relationships_response.ex
@@ -0,0 +1,55 @@
+# 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.AccountRelationshipsResponse do
+  require OpenApiSpex
+
+  OpenApiSpex.schema(%{
+    title: "AccountRelationshipsResponse",
+    description: "Response schema for account relationships",
+    type: :array,
+    items: Pleroma.Web.ApiSpec.Schemas.AccountRelationshipResponse,
+    example: [
+      %{
+        "id" => "1",
+        "following" => true,
+        "showing_reblogs" => true,
+        "followed_by" => true,
+        "blocking" => false,
+        "blocked_by" => true,
+        "muting" => false,
+        "muting_notifications" => false,
+        "requested" => false,
+        "domain_blocking" => false,
+        "endorsed" => true
+      },
+      %{
+        "id" => "2",
+        "following" => true,
+        "showing_reblogs" => true,
+        "followed_by" => true,
+        "blocking" => false,
+        "blocked_by" => true,
+        "muting" => true,
+        "muting_notifications" => false,
+        "requested" => true,
+        "domain_blocking" => false,
+        "endorsed" => false
+      },
+      %{
+        "id" => "3",
+        "following" => true,
+        "showing_reblogs" => true,
+        "followed_by" => true,
+        "blocking" => true,
+        "blocked_by" => false,
+        "muting" => true,
+        "muting_notifications" => false,
+        "requested" => false,
+        "domain_blocking" => true,
+        "endorsed" => false
+      }
+    ]
+  })
+end
diff --git a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
index 9c986b3b2..1652e3a1b 100644
--- a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
@@ -83,7 +83,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
   plug(
     OpenApiSpex.Plug.CastAndValidate,
     [render_error: Pleroma.Web.ApiSpec.RenderError]
-    when action in [:create, :verify_credentials, :update_credentials]
+    when action in [:create, :verify_credentials, :update_credentials, :relationships]
   )
 
   action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
@@ -229,7 +229,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
   end
 
   @doc "GET /api/v1/accounts/relationships"
-  def relationships(%{assigns: %{user: user}} = conn, %{"id" => id}) do
+  def relationships(%{assigns: %{user: user}} = conn, %{id: id}) do
     targets = User.get_all_by_ids(List.wrap(id))
 
     render(conn, "relationships.json", user: user, targets: targets)
diff --git a/test/web/api_spec/account_operation_test.exs b/test/web/api_spec/account_operation_test.exs
index a54059074..58a38d8af 100644
--- a/test/web/api_spec/account_operation_test.exs
+++ b/test/web/api_spec/account_operation_test.exs
@@ -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.AccountRelationshipsResponse
   alias Pleroma.Web.ApiSpec.Schemas.AccountUpdateCredentialsRequest
 
   import OpenApiSpex.TestAssertions
@@ -84,4 +85,27 @@ defmodule Pleroma.Web.ApiSpec.AccountOperationTest do
 
     assert_schema(json, "Account", api_spec)
   end
+
+  test "AccountRelationshipsResponse example matches schema" do
+    api_spec = ApiSpec.spec()
+    schema = AccountRelationshipsResponse.schema()
+    assert_schema(schema.example, "AccountRelationshipsResponse", api_spec)
+  end
+
+  test "/api/v1/accounts/relationships produces AccountRelationshipsResponse", %{
+    conn: conn
+  } do
+    token = insert(:oauth_token, scopes: ["read", "write"])
+    other_user = insert(:user)
+    {:ok, _user} = Pleroma.User.follow(token.user, other_user)
+    api_spec = ApiSpec.spec()
+
+    assert [relationship] =
+             conn
+             |> put_req_header("authorization", "Bearer " <> token.token)
+             |> get("/api/v1/accounts/relationships?id=#{other_user.id}")
+             |> json_response(:ok)
+
+    assert_schema([relationship], "AccountRelationshipsResponse", api_spec)
+  end
 end
diff --git a/test/web/mastodon_api/controllers/account_controller_test.exs b/test/web/mastodon_api/controllers/account_controller_test.exs
index 6fe46af3c..060a7c1cd 100644
--- a/test/web/mastodon_api/controllers/account_controller_test.exs
+++ b/test/web/mastodon_api/controllers/account_controller_test.exs
@@ -1062,14 +1062,18 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
     setup do: oauth_access(["read:follows"])
 
     test "returns the relationships for the current user", %{user: user, conn: conn} do
-      other_user = insert(:user)
+      %{id: other_user_id} = other_user = insert(:user)
       {:ok, _user} = User.follow(user, other_user)
 
-      conn = get(conn, "/api/v1/accounts/relationships", %{"id" => [other_user.id]})
+      assert [%{"id" => ^other_user_id}] =
+               conn
+               |> get("/api/v1/accounts/relationships?id=#{other_user.id}")
+               |> json_response(200)
 
-      assert [relationship] = json_response(conn, 200)
-
-      assert to_string(other_user.id) == relationship["id"]
+      assert [%{"id" => ^other_user_id}] =
+               conn
+               |> get("/api/v1/accounts/relationships?id[]=#{other_user.id}")
+               |> json_response(200)
     end
 
     test "returns an empty list on a bad request", %{conn: conn} do

From 278b3fa0ad0ca58a9e5549e98d24944bbe0bf766 Mon Sep 17 00:00:00 2001
From: Egor Kislitsyn <egor@kislitsyn.com>
Date: Tue, 7 Apr 2020 18:53:12 +0400
Subject: [PATCH 06/40] Add spec for AccountController.show

---
 .../web/api_spec/operations/account_operation.ex | 16 +++++++++++++++-
 .../controllers/account_controller.ex            |  4 ++--
 test/web/api_spec/account_operation_test.exs     | 16 +++++++++++++++-
 3 files changed, 32 insertions(+), 4 deletions(-)

diff --git a/lib/pleroma/web/api_spec/operations/account_operation.ex b/lib/pleroma/web/api_spec/operations/account_operation.ex
index 352f66e9d..5b1b2eb4c 100644
--- a/lib/pleroma/web/api_spec/operations/account_operation.ex
+++ b/lib/pleroma/web/api_spec/operations/account_operation.ex
@@ -86,7 +86,21 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
   end
 
   def show_operation do
-    :ok
+    %Operation{
+      tags: ["accounts"],
+      summary: "Account",
+      operationId: "AccountController.show",
+      description: "View information about a profile.",
+      parameters: [
+        Operation.parameter(:id, :path, :string, "Account ID or nickname",
+          example: "123",
+          required: true
+        )
+      ],
+      responses: %{
+        200 => Operation.response("Account", "application/json", Account)
+      }
+    }
   end
 
   def statuses_operation do
diff --git a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
index 1652e3a1b..67375f31c 100644
--- a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
@@ -83,7 +83,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
   plug(
     OpenApiSpex.Plug.CastAndValidate,
     [render_error: Pleroma.Web.ApiSpec.RenderError]
-    when action in [:create, :verify_credentials, :update_credentials, :relationships]
+    when action in [:create, :verify_credentials, :update_credentials, :relationships, :show]
   )
 
   action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
@@ -239,7 +239,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
   def relationships(%{assigns: %{user: _user}} = conn, _), do: json(conn, [])
 
   @doc "GET /api/v1/accounts/:id"
-  def show(%{assigns: %{user: for_user}} = conn, %{"id" => nickname_or_id}) do
+  def show(%{assigns: %{user: for_user}} = conn, %{id: nickname_or_id}) do
     with %User{} = user <- User.get_cached_by_nickname_or_id(nickname_or_id, for: for_user),
          true <- User.visible_for?(user, for_user) do
       render(conn, "show.json", user: user, for: for_user)
diff --git a/test/web/api_spec/account_operation_test.exs b/test/web/api_spec/account_operation_test.exs
index 58a38d8af..6cc08ee0e 100644
--- a/test/web/api_spec/account_operation_test.exs
+++ b/test/web/api_spec/account_operation_test.exs
@@ -3,7 +3,7 @@
 # SPDX-License-Identifier: AGPL-3.0-only
 
 defmodule Pleroma.Web.ApiSpec.AccountOperationTest do
-  use Pleroma.Web.ConnCase, async: true
+  use Pleroma.Web.ConnCase
 
   alias Pleroma.Web.ApiSpec
   alias Pleroma.Web.ApiSpec.Schemas.Account
@@ -108,4 +108,18 @@ defmodule Pleroma.Web.ApiSpec.AccountOperationTest do
 
     assert_schema([relationship], "AccountRelationshipsResponse", api_spec)
   end
+
+  test "/api/v1/accounts/:id produces Account", %{
+    conn: conn
+  } do
+    user = insert(:user)
+    api_spec = ApiSpec.spec()
+
+    assert resp =
+             conn
+             |> get("/api/v1/accounts/#{user.id}")
+             |> json_response(:ok)
+
+    assert_schema(resp, "Account", api_spec)
+  end
 end

From 03124c96cc192ef8c4893738a0cee552c6984da6 Mon Sep 17 00:00:00 2001
From: Egor Kislitsyn <egor@kislitsyn.com>
Date: Wed, 8 Apr 2020 22:33:25 +0400
Subject: [PATCH 07/40] Add spec for AccountController.statuses

---
 lib/pleroma/web/activity_pub/activity_pub.ex  |  11 +-
 lib/pleroma/web/api_spec.ex                   |   8 +
 .../api_spec/operations/account_operation.ex  |  45 +++-
 .../account_update_credentials_request.ex     |   5 +-
 .../web/api_spec/schemas/boolean_like.ex      |  36 +++
 lib/pleroma/web/api_spec/schemas/poll.ex      |  35 +++
 lib/pleroma/web/api_spec/schemas/status.ex    | 227 ++++++++++++++++++
 .../web/api_spec/schemas/statuses_response.ex |  13 +
 .../controllers/account_controller.ex         |  17 +-
 .../web/mastodon_api/views/status_view.ex     |   8 +-
 mix.exs                                       |   4 +-
 mix.lock                                      |   4 +-
 test/web/api_spec/account_operation_test.exs  |  16 ++
 .../controllers/account_controller_test.exs   |  60 +++--
 14 files changed, 444 insertions(+), 45 deletions(-)
 create mode 100644 lib/pleroma/web/api_spec/schemas/boolean_like.ex
 create mode 100644 lib/pleroma/web/api_spec/schemas/poll.ex
 create mode 100644 lib/pleroma/web/api_spec/schemas/status.ex
 create mode 100644 lib/pleroma/web/api_spec/schemas/statuses_response.ex

diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex
index 86b105b7f..1909ce097 100644
--- a/lib/pleroma/web/activity_pub/activity_pub.ex
+++ b/lib/pleroma/web/activity_pub/activity_pub.ex
@@ -853,7 +853,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
   end
 
   defp exclude_visibility(query, %{"exclude_visibilities" => visibility})
-       when visibility not in @valid_visibilities do
+       when visibility not in [nil | @valid_visibilities] do
     Logger.error("Could not exclude visibility to #{visibility}")
     query
   end
@@ -1060,7 +1060,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
     raise "Can't use the child object without preloading!"
   end
 
-  defp restrict_media(query, %{"only_media" => val}) when val == "true" or val == "1" do
+  defp restrict_media(query, %{"only_media" => val}) when val in [true, "true", "1"] do
     from(
       [_activity, object] in query,
       where: fragment("not (?)->'attachment' = (?)", object.data, ^[])
@@ -1069,7 +1069,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
 
   defp restrict_media(query, _), do: query
 
-  defp restrict_replies(query, %{"exclude_replies" => val}) when val == "true" or val == "1" do
+  defp restrict_replies(query, %{"exclude_replies" => val}) when val in [true, "true", "1"] do
     from(
       [_activity, object] in query,
       where: fragment("?->>'inReplyTo' is null", object.data)
@@ -1078,7 +1078,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
 
   defp restrict_replies(query, _), do: query
 
-  defp restrict_reblogs(query, %{"exclude_reblogs" => val}) when val == "true" or val == "1" do
+  defp restrict_reblogs(query, %{"exclude_reblogs" => val}) when val in [true, "true", "1"] do
     from(activity in query, where: fragment("?->>'type' != 'Announce'", activity.data))
   end
 
@@ -1157,7 +1157,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
     )
   end
 
-  defp restrict_pinned(query, %{"pinned" => "true", "pinned_activity_ids" => ids}) do
+  defp restrict_pinned(query, %{"pinned" => pinned, "pinned_activity_ids" => ids})
+       when pinned in [true, "true", "1"] do
     from(activity in query, where: activity.id in ^ids)
   end
 
diff --git a/lib/pleroma/web/api_spec.ex b/lib/pleroma/web/api_spec.ex
index c85fe30d1..d11e776d0 100644
--- a/lib/pleroma/web/api_spec.ex
+++ b/lib/pleroma/web/api_spec.ex
@@ -4,6 +4,7 @@
 
 defmodule Pleroma.Web.ApiSpec do
   alias OpenApiSpex.OpenApi
+  alias OpenApiSpex.Operation
   alias Pleroma.Web.Endpoint
   alias Pleroma.Web.Router
 
@@ -24,6 +25,13 @@ defmodule Pleroma.Web.ApiSpec do
       # populate the paths from a phoenix router
       paths: OpenApiSpex.Paths.from_router(Router),
       components: %OpenApiSpex.Components{
+        parameters: %{
+          "accountIdOrNickname" =>
+            Operation.parameter(:id, :path, :string, "Account ID or nickname",
+              example: "123",
+              required: true
+            )
+        },
         securitySchemes: %{
           "oAuth" => %OpenApiSpex.SecurityScheme{
             type: "oauth2",
diff --git a/lib/pleroma/web/api_spec/operations/account_operation.ex b/lib/pleroma/web/api_spec/operations/account_operation.ex
index 5b1b2eb4c..09e6d24ed 100644
--- a/lib/pleroma/web/api_spec/operations/account_operation.ex
+++ b/lib/pleroma/web/api_spec/operations/account_operation.ex
@@ -4,6 +4,7 @@
 
 defmodule Pleroma.Web.ApiSpec.AccountOperation do
   alias OpenApiSpex.Operation
+  alias OpenApiSpex.Reference
   alias OpenApiSpex.Schema
   alias Pleroma.Web.ApiSpec.Helpers
   alias Pleroma.Web.ApiSpec.Schemas.Account
@@ -11,6 +12,9 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
   alias Pleroma.Web.ApiSpec.Schemas.AccountCreateResponse
   alias Pleroma.Web.ApiSpec.Schemas.AccountRelationshipsResponse
   alias Pleroma.Web.ApiSpec.Schemas.AccountUpdateCredentialsRequest
+  alias Pleroma.Web.ApiSpec.Schemas.BooleanLike
+  alias Pleroma.Web.ApiSpec.Schemas.StatusesResponse
+  alias Pleroma.Web.ApiSpec.Schemas.VisibilityScope
 
   @spec open_api_operation(atom) :: Operation.t()
   def open_api_operation(action) do
@@ -91,12 +95,7 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
       summary: "Account",
       operationId: "AccountController.show",
       description: "View information about a profile.",
-      parameters: [
-        Operation.parameter(:id, :path, :string, "Account ID or nickname",
-          example: "123",
-          required: true
-        )
-      ],
+      parameters: [%Reference{"$ref": "#/components/parameters/accountIdOrNickname"}],
       responses: %{
         200 => Operation.response("Account", "application/json", Account)
       }
@@ -104,7 +103,39 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
   end
 
   def statuses_operation do
-    :ok
+    %Operation{
+      tags: ["accounts"],
+      summary: "Statuses",
+      operationId: "AccountController.statuses",
+      description:
+        "Statuses posted to the given account. Public (for public statuses only), or user token + `read:statuses` (for private statuses the user is authorized to see)",
+      parameters: [
+        %Reference{"$ref": "#/components/parameters/accountIdOrNickname"},
+        Operation.parameter(:pinned, :query, BooleanLike, "Pinned"),
+        Operation.parameter(:tagged, :query, :string, "With tag"),
+        Operation.parameter(:only_media, :query, BooleanLike, "Only meadia"),
+        Operation.parameter(:with_muted, :query, BooleanLike, "With muted"),
+        Operation.parameter(:exclude_reblogs, :query, BooleanLike, "Exclude reblobs"),
+        Operation.parameter(
+          :exclude_visibilities,
+          :query,
+          %Schema{type: :array, items: VisibilityScope},
+          "Exclude visibilities"
+        ),
+        Operation.parameter(:max_id, :query, :string, "Max ID"),
+        Operation.parameter(:min_id, :query, :string, "Mix ID"),
+        Operation.parameter(:since_id, :query, :string, "Since ID"),
+        Operation.parameter(
+          :limit,
+          :query,
+          %Schema{type: :integer, default: 20, maximum: 40},
+          "Limit"
+        )
+      ],
+      responses: %{
+        200 => Operation.response("Statuses", "application/json", StatusesResponse)
+      }
+    }
   end
 
   def followers_operation do
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
index 6ab48193e..35220c78a 100644
--- a/lib/pleroma/web/api_spec/schemas/account_update_credentials_request.ex
+++ b/lib/pleroma/web/api_spec/schemas/account_update_credentials_request.ex
@@ -38,7 +38,10 @@ defmodule Pleroma.Web.ApiSpec.Schemas.AccountUpdateCredentialsRequest do
         description: "Whether manual approval of follow requests is required."
       },
       fields_attributes: %Schema{
-        oneOf: [%Schema{type: :array, items: AccountAttributeField}, %Schema{type: :object}]
+        oneOf: [
+          %Schema{type: :array, items: AccountAttributeField},
+          %Schema{type: :object, additionalProperties: %Schema{type: AccountAttributeField}}
+        ]
       },
       # NOTE: `source` field is not supported
       #
diff --git a/lib/pleroma/web/api_spec/schemas/boolean_like.ex b/lib/pleroma/web/api_spec/schemas/boolean_like.ex
new file mode 100644
index 000000000..f3bfb74da
--- /dev/null
+++ b/lib/pleroma/web/api_spec/schemas/boolean_like.ex
@@ -0,0 +1,36 @@
+# 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.BooleanLike do
+  alias OpenApiSpex.Schema
+
+  require OpenApiSpex
+
+  OpenApiSpex.schema(%{
+    title: "BooleanLike",
+    description: """
+    The following values will be treated as `false`:
+      - false
+      - 0
+      - "0",
+      - "f",
+      - "F",
+      - "false",
+      - "FALSE",
+      - "off",
+      - "OFF"
+
+    All other non-null values will be treated as `true`
+    """,
+    anyOf: [
+      %Schema{type: :boolean},
+      %Schema{type: :string},
+      %Schema{type: :integer}
+    ]
+  })
+
+  def after_cast(value, _schmea) do
+    {:ok, Pleroma.Web.ControllerHelper.truthy_param?(value)}
+  end
+end
diff --git a/lib/pleroma/web/api_spec/schemas/poll.ex b/lib/pleroma/web/api_spec/schemas/poll.ex
new file mode 100644
index 000000000..2a9975f85
--- /dev/null
+++ b/lib/pleroma/web/api_spec/schemas/poll.ex
@@ -0,0 +1,35 @@
+# 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.Poll do
+  alias OpenApiSpex.Schema
+  alias Pleroma.Web.ApiSpec.Schemas.AccountEmoji
+
+  require OpenApiSpex
+
+  OpenApiSpex.schema(%{
+    title: "Poll",
+    description: "Response schema for account custom fields",
+    type: :object,
+    properties: %{
+      id: %Schema{type: :string},
+      expires_at: %Schema{type: :string, format: "date-time"},
+      expired: %Schema{type: :boolean},
+      multiple: %Schema{type: :boolean},
+      votes_count: %Schema{type: :integer},
+      voted: %Schema{type: :boolean},
+      emojis: %Schema{type: :array, items: AccountEmoji},
+      options: %Schema{
+        type: :array,
+        items: %Schema{
+          type: :object,
+          properties: %{
+            title: %Schema{type: :string},
+            votes_count: %Schema{type: :integer}
+          }
+        }
+      }
+    }
+  })
+end
diff --git a/lib/pleroma/web/api_spec/schemas/status.ex b/lib/pleroma/web/api_spec/schemas/status.ex
new file mode 100644
index 000000000..486c3a0fe
--- /dev/null
+++ b/lib/pleroma/web/api_spec/schemas/status.ex
@@ -0,0 +1,227 @@
+# 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.Status do
+  alias OpenApiSpex.Schema
+  alias Pleroma.Web.ApiSpec.Schemas.Account
+  alias Pleroma.Web.ApiSpec.Schemas.AccountEmoji
+  alias Pleroma.Web.ApiSpec.Schemas.Poll
+  alias Pleroma.Web.ApiSpec.Schemas.VisibilityScope
+
+  require OpenApiSpex
+
+  OpenApiSpex.schema(%{
+    title: "Status",
+    description: "Response schema for a status",
+    type: :object,
+    properties: %{
+      account: Account,
+      application: %Schema{
+        type: :object,
+        properties: %{
+          name: %Schema{type: :string},
+          website: %Schema{type: :string, nullable: true}
+        }
+      },
+      bookmarked: %Schema{type: :boolean},
+      card: %Schema{
+        type: :object,
+        nullable: true,
+        properties: %{
+          type: %Schema{type: :string},
+          provider_name: %Schema{type: :string},
+          provider_url: %Schema{type: :string},
+          url: %Schema{type: :string},
+          image: %Schema{type: :string},
+          title: %Schema{type: :string},
+          description: %Schema{type: :string}
+        }
+      },
+      content: %Schema{type: :string},
+      created_at: %Schema{type: :string, format: "date-time"},
+      emojis: %Schema{type: :array, items: AccountEmoji},
+      favourited: %Schema{type: :boolean},
+      favourites_count: %Schema{type: :integer},
+      id: %Schema{type: :string},
+      in_reply_to_account_id: %Schema{type: :string, nullable: true},
+      in_reply_to_id: %Schema{type: :string, nullable: true},
+      language: %Schema{type: :string, nullable: true},
+      media_attachments: %Schema{
+        type: :array,
+        items: %Schema{
+          type: :object,
+          properties: %{
+            id: %Schema{type: :string},
+            url: %Schema{type: :string},
+            remote_url: %Schema{type: :string},
+            preview_url: %Schema{type: :string},
+            text_url: %Schema{type: :string},
+            description: %Schema{type: :string},
+            type: %Schema{type: :string, enum: ["image", "video", "audio", "unknown"]},
+            pleroma: %Schema{
+              type: :object,
+              properties: %{mime_type: %Schema{type: :string}}
+            }
+          }
+        }
+      },
+      mentions: %Schema{
+        type: :array,
+        items: %Schema{
+          type: :object,
+          properties: %{
+            id: %Schema{type: :string},
+            acct: %Schema{type: :string},
+            username: %Schema{type: :string},
+            url: %Schema{type: :string}
+          }
+        }
+      },
+      muted: %Schema{type: :boolean},
+      pinned: %Schema{type: :boolean},
+      pleroma: %Schema{
+        type: :object,
+        properties: %{
+          content: %Schema{type: :object, additionalProperties: %Schema{type: :string}},
+          conversation_id: %Schema{type: :integer},
+          direct_conversation_id: %Schema{type: :string, nullable: true},
+          emoji_reactions: %Schema{
+            type: :array,
+            items: %Schema{
+              type: :object,
+              properties: %{
+                name: %Schema{type: :string},
+                count: %Schema{type: :integer},
+                me: %Schema{type: :boolean}
+              }
+            }
+          },
+          expires_at: %Schema{type: :string, format: "date-time", nullable: true},
+          in_reply_to_account_acct: %Schema{type: :string, nullable: true},
+          local: %Schema{type: :boolean},
+          spoiler_text: %Schema{type: :object, additionalProperties: %Schema{type: :string}},
+          thread_muted: %Schema{type: :boolean}
+        }
+      },
+      poll: %Schema{type: Poll, nullable: true},
+      reblog: %Schema{
+        allOf: [%OpenApiSpex.Reference{"$ref": "#/components/schemas/Status"}],
+        nullable: true
+      },
+      reblogged: %Schema{type: :boolean},
+      reblogs_count: %Schema{type: :integer},
+      replies_count: %Schema{type: :integer},
+      sensitive: %Schema{type: :boolean},
+      spoiler_text: %Schema{type: :string},
+      tags: %Schema{
+        type: :array,
+        items: %Schema{
+          type: :object,
+          properties: %{
+            name: %Schema{type: :string},
+            url: %Schema{type: :string}
+          }
+        }
+      },
+      uri: %Schema{type: :string},
+      url: %Schema{type: :string},
+      visibility: VisibilityScope
+    },
+    example: %{
+      "JSON" => %{
+        "account" => %{
+          "acct" => "nick6",
+          "avatar" => "http://localhost:4001/images/avi.png",
+          "avatar_static" => "http://localhost:4001/images/avi.png",
+          "bot" => false,
+          "created_at" => "2020-04-07T19:48:51.000Z",
+          "display_name" => "Test テスト User 6",
+          "emojis" => [],
+          "fields" => [],
+          "followers_count" => 1,
+          "following_count" => 0,
+          "header" => "http://localhost:4001/images/banner.png",
+          "header_static" => "http://localhost:4001/images/banner.png",
+          "id" => "9toJCsKN7SmSf3aj5c",
+          "locked" => false,
+          "note" => "Tester Number 6",
+          "pleroma" => %{
+            "background_image" => nil,
+            "confirmation_pending" => false,
+            "hide_favorites" => true,
+            "hide_followers" => false,
+            "hide_followers_count" => false,
+            "hide_follows" => false,
+            "hide_follows_count" => false,
+            "is_admin" => false,
+            "is_moderator" => false,
+            "relationship" => %{
+              "blocked_by" => false,
+              "blocking" => false,
+              "domain_blocking" => false,
+              "endorsed" => false,
+              "followed_by" => false,
+              "following" => true,
+              "id" => "9toJCsKN7SmSf3aj5c",
+              "muting" => false,
+              "muting_notifications" => false,
+              "requested" => false,
+              "showing_reblogs" => true,
+              "subscribing" => false
+            },
+            "skip_thread_containment" => false,
+            "tags" => []
+          },
+          "source" => %{
+            "fields" => [],
+            "note" => "Tester Number 6",
+            "pleroma" => %{"actor_type" => "Person", "discoverable" => false},
+            "sensitive" => false
+          },
+          "statuses_count" => 1,
+          "url" => "http://localhost:4001/users/nick6",
+          "username" => "nick6"
+        },
+        "application" => %{"name" => "Web", "website" => nil},
+        "bookmarked" => false,
+        "card" => nil,
+        "content" => "foobar",
+        "created_at" => "2020-04-07T19:48:51.000Z",
+        "emojis" => [],
+        "favourited" => false,
+        "favourites_count" => 0,
+        "id" => "9toJCu5YZW7O7gfvH6",
+        "in_reply_to_account_id" => nil,
+        "in_reply_to_id" => nil,
+        "language" => nil,
+        "media_attachments" => [],
+        "mentions" => [],
+        "muted" => false,
+        "pinned" => false,
+        "pleroma" => %{
+          "content" => %{"text/plain" => "foobar"},
+          "conversation_id" => 345_972,
+          "direct_conversation_id" => nil,
+          "emoji_reactions" => [],
+          "expires_at" => nil,
+          "in_reply_to_account_acct" => nil,
+          "local" => true,
+          "spoiler_text" => %{"text/plain" => ""},
+          "thread_muted" => false
+        },
+        "poll" => nil,
+        "reblog" => nil,
+        "reblogged" => false,
+        "reblogs_count" => 0,
+        "replies_count" => 0,
+        "sensitive" => false,
+        "spoiler_text" => "",
+        "tags" => [],
+        "uri" => "http://localhost:4001/objects/0f5dad44-0e9e-4610-b377-a2631e499190",
+        "url" => "http://localhost:4001/notice/9toJCu5YZW7O7gfvH6",
+        "visibility" => "private"
+      }
+    }
+  })
+end
diff --git a/lib/pleroma/web/api_spec/schemas/statuses_response.ex b/lib/pleroma/web/api_spec/schemas/statuses_response.ex
new file mode 100644
index 000000000..fb7c7e0aa
--- /dev/null
+++ b/lib/pleroma/web/api_spec/schemas/statuses_response.ex
@@ -0,0 +1,13 @@
+# 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.StatusesResponse do
+  require OpenApiSpex
+
+  OpenApiSpex.schema(%{
+    title: "StatusesResponse",
+    type: :array,
+    items: Pleroma.Web.ApiSpec.Schemas.Status
+  })
+end
diff --git a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
index 67375f31c..208df5698 100644
--- a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
@@ -83,7 +83,14 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
   plug(
     OpenApiSpex.Plug.CastAndValidate,
     [render_error: Pleroma.Web.ApiSpec.RenderError]
-    when action in [:create, :verify_credentials, :update_credentials, :relationships, :show]
+    when action in [
+           :create,
+           :verify_credentials,
+           :update_credentials,
+           :relationships,
+           :show,
+           :statuses
+         ]
   )
 
   action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
@@ -250,12 +257,14 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
 
   @doc "GET /api/v1/accounts/:id/statuses"
   def statuses(%{assigns: %{user: reading_user}} = conn, params) do
-    with %User{} = user <- User.get_cached_by_nickname_or_id(params["id"], for: reading_user),
+    with %User{} = user <- User.get_cached_by_nickname_or_id(params.id, for: reading_user),
          true <- User.visible_for?(user, reading_user) do
       params =
         params
-        |> Map.put("tag", params["tagged"])
-        |> Map.delete("godmode")
+        |> Map.delete(:tagged)
+        |> Enum.filter(&(not is_nil(&1)))
+        |> Map.new(fn {key, value} -> {to_string(key), value} end)
+        |> Map.put("tag", params[:tagged])
 
       activities = ActivityPub.fetch_user_activities(user, reading_user, params)
 
diff --git a/lib/pleroma/web/mastodon_api/views/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex
index b5850e1ae..ba40fd63e 100644
--- a/lib/pleroma/web/mastodon_api/views/status_view.ex
+++ b/lib/pleroma/web/mastodon_api/views/status_view.ex
@@ -521,11 +521,9 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
   """
   @spec build_tags(list(any())) :: list(map())
   def build_tags(object_tags) when is_list(object_tags) do
-    object_tags = for tag when is_binary(tag) <- object_tags, do: tag
-
-    Enum.reduce(object_tags, [], fn tag, tags ->
-      tags ++ [%{name: tag, url: "/tag/#{URI.encode(tag)}"}]
-    end)
+    object_tags
+    |> Enum.filter(&is_binary/1)
+    |> Enum.map(&%{name: &1, url: "/tag/#{URI.encode(&1)}"})
   end
 
   def build_tags(_), do: []
diff --git a/mix.exs b/mix.exs
index c781995e0..ec69d70c0 100644
--- a/mix.exs
+++ b/mix.exs
@@ -189,7 +189,9 @@ defmodule Pleroma.Mixfile do
        ref: "e0f16822d578866e186a0974d65ad58cddc1e2ab"},
       {:mox, "~> 0.5", only: :test},
       {:restarter, path: "./restarter"},
-      {:open_api_spex, "~> 3.6"}
+      {:open_api_spex,
+       git: "https://git.pleroma.social/pleroma/elixir-libraries/open_api_spex.git",
+       ref: "b862ebd78de0df95875cf46feb6e9607130dc2a8"}
     ] ++ oauth_deps()
   end
 
diff --git a/mix.lock b/mix.lock
index ba4e3ac44..779be4f87 100644
--- a/mix.lock
+++ b/mix.lock
@@ -74,7 +74,7 @@
   "nimble_parsec": {:hex, :nimble_parsec, "0.5.3", "def21c10a9ed70ce22754fdeea0810dafd53c2db3219a0cd54cf5526377af1c6", [:mix], [], "hexpm", "589b5af56f4afca65217a1f3eb3fee7e79b09c40c742fddc1c312b3ac0b3399f"},
   "nodex": {:git, "https://git.pleroma.social/pleroma/nodex", "cb6730f943cfc6aad674c92161be23a8411f15d1", [ref: "cb6730f943cfc6aad674c92161be23a8411f15d1"]},
   "oban": {:hex, :oban, "1.2.0", "7cca94d341be43d220571e28f69131c4afc21095b25257397f50973d3fc59b07", [:mix], [{:ecto_sql, "~> 3.1", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.14", [hex: :postgrex, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ba5f8b3f7d76967b3e23cf8014f6a13e4ccb33431e4808f036709a7f822362ee"},
-  "open_api_spex": {:hex, :open_api_spex, "3.6.0", "64205aba9f2607f71b08fd43e3351b9c5e9898ec5ef49fc0ae35890da502ade9", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:poison, "~> 3.1", [hex: :poison, repo: "hexpm", optional: true]}], "hexpm", "126ba3473966277132079cb1d5bf1e3df9e36fe2acd00166e75fd125cecb59c5"},
+  "open_api_spex": {:git, "https://git.pleroma.social/pleroma/elixir-libraries/open_api_spex.git", "b862ebd78de0df95875cf46feb6e9607130dc2a8", [ref: "b862ebd78de0df95875cf46feb6e9607130dc2a8"]},
   "parse_trans": {:hex, :parse_trans, "3.3.0", "09765507a3c7590a784615cfd421d101aec25098d50b89d7aa1d66646bc571c1", [:rebar3], [], "hexpm", "17ef63abde837ad30680ea7f857dd9e7ced9476cdd7b0394432af4bfc241b960"},
   "pbkdf2_elixir": {:hex, :pbkdf2_elixir, "0.12.4", "8dd29ed783f2e12195d7e0a4640effc0a7c37e6537da491f1db01839eee6d053", [:mix], [], "hexpm", "595d09db74cb093b1903381c9de423276a931a2480a46a1a5dc7f932a2a6375b"},
   "phoenix": {:hex, :phoenix, "1.4.13", "67271ad69b51f3719354604f4a3f968f83aa61c19199343656c9caee057ff3b8", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 1.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug, "~> 1.8.1 or ~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 1.0 or ~> 2.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ab765a0feddb81fc62e2116c827b5f068df85159c162bee760745276ad7ddc1b"},
@@ -82,7 +82,7 @@
   "phoenix_html": {:hex, :phoenix_html, "2.14.0", "d8c6bc28acc8e65f8ea0080ee05aa13d912c8758699283b8d3427b655aabe284", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "b0bb30eda478a06dbfbe96728061a93833db3861a49ccb516f839ecb08493fbb"},
   "phoenix_pubsub": {:hex, :phoenix_pubsub, "1.1.2", "496c303bdf1b2e98a9d26e89af5bba3ab487ba3a3735f74bf1f4064d2a845a3e", [:mix], [], "hexpm", "1f13f9f0f3e769a667a6b6828d29dec37497a082d195cc52dbef401a9b69bf38"},
   "phoenix_swoosh": {:hex, :phoenix_swoosh, "0.2.0", "a7e0b32077cd6d2323ae15198839b05d9caddfa20663fd85787479e81f89520e", [:mix], [{:phoenix, "~> 1.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.2", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:swoosh, "~> 0.1", [hex: :swoosh, repo: "hexpm", optional: false]}], "hexpm", "ebf1bfa7b3c1c850c04929afe02e2e0d7ab135e0706332c865de03e761676b1f"},
-  "plug": {:hex, :plug, "1.9.0", "8d7c4e26962283ff9f8f3347bd73838e2413fbc38b7bb5467d5924f68f3a5a4a", [:mix], [{:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "9902eda2c52ada2a096434682e99a2493f5d06a94d6ac6bcfff9805f952350f1"},
+  "plug": {:hex, :plug, "1.10.0", "6508295cbeb4c654860845fb95260737e4a8838d34d115ad76cd487584e2fc4d", [:mix], [{:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "422a9727e667be1bf5ab1de03be6fa0ad67b775b2d84ed908f3264415ef29d4a"},
   "plug_cowboy": {:hex, :plug_cowboy, "2.1.2", "8b0addb5908c5238fac38e442e81b6fcd32788eaa03246b4d55d147c47c5805e", [:mix], [{:cowboy, "~> 2.5", [hex: :cowboy, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "7d722581ce865a237e14da6d946f92704101740a256bd13ec91e63c0b122fc70"},
   "plug_crypto": {:hex, :plug_crypto, "1.1.2", "bdd187572cc26dbd95b87136290425f2b580a116d3fb1f564216918c9730d227", [:mix], [], "hexpm", "6b8b608f895b6ffcfad49c37c7883e8df98ae19c6a28113b02aa1e9c5b22d6b5"},
   "plug_static_index_html": {:hex, :plug_static_index_html, "1.0.0", "840123d4d3975585133485ea86af73cb2600afd7f2a976f9f5fd8b3808e636a0", [:mix], [{:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "79fd4fcf34d110605c26560cbae8f23c603ec4158c08298bd4360fdea90bb5cf"},
diff --git a/test/web/api_spec/account_operation_test.exs b/test/web/api_spec/account_operation_test.exs
index 6cc08ee0e..892ade71c 100644
--- a/test/web/api_spec/account_operation_test.exs
+++ b/test/web/api_spec/account_operation_test.exs
@@ -122,4 +122,20 @@ defmodule Pleroma.Web.ApiSpec.AccountOperationTest do
 
     assert_schema(resp, "Account", api_spec)
   end
+
+  test "/api/v1/accounts/:id/statuses produces StatusesResponse", %{
+    conn: conn
+  } do
+    user = insert(:user)
+    Pleroma.Web.CommonAPI.post(user, %{"status" => "foobar"})
+
+    api_spec = ApiSpec.spec()
+
+    assert resp =
+             conn
+             |> get("/api/v1/accounts/#{user.id}/statuses")
+             |> json_response(:ok)
+
+    assert_schema(resp, "StatusesResponse", api_spec)
+  end
 end
diff --git a/test/web/mastodon_api/controllers/account_controller_test.exs b/test/web/mastodon_api/controllers/account_controller_test.exs
index 060a7c1cd..969256fa4 100644
--- a/test/web/mastodon_api/controllers/account_controller_test.exs
+++ b/test/web/mastodon_api/controllers/account_controller_test.exs
@@ -10,9 +10,11 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
   alias Pleroma.User
   alias Pleroma.Web.ActivityPub.ActivityPub
   alias Pleroma.Web.ActivityPub.InternalFetchActor
+  alias Pleroma.Web.ApiSpec
   alias Pleroma.Web.CommonAPI
   alias Pleroma.Web.OAuth.Token
 
+  import OpenApiSpex.TestAssertions
   import Pleroma.Factory
 
   describe "account fetching" do
@@ -245,22 +247,23 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
       {:ok, activity} = CommonAPI.post(user_two, %{"status" => "User one sux0rz"})
       {:ok, repeat, _} = CommonAPI.repeat(activity.id, user_three)
 
-      resp = get(conn, "/api/v1/accounts/#{user_two.id}/statuses")
-
-      assert [%{"id" => id}] = json_response(resp, 200)
+      assert resp = get(conn, "/api/v1/accounts/#{user_two.id}/statuses") |> json_response(200)
+      assert [%{"id" => id}] = resp
+      assert_schema(resp, "StatusesResponse", ApiSpec.spec())
       assert id == activity.id
 
       # Even a blocked user will deliver the full user timeline, there would be
       #   no point in looking at a blocked users timeline otherwise
-      resp = get(conn, "/api/v1/accounts/#{user_two.id}/statuses")
-
-      assert [%{"id" => id}] = json_response(resp, 200)
+      assert resp = get(conn, "/api/v1/accounts/#{user_two.id}/statuses") |> json_response(200)
+      assert [%{"id" => id}] = resp
       assert id == activity.id
+      assert_schema(resp, "StatusesResponse", ApiSpec.spec())
 
       # Third user's timeline includes the repeat when viewed by unauthenticated user
-      resp = get(build_conn(), "/api/v1/accounts/#{user_three.id}/statuses")
-      assert [%{"id" => id}] = json_response(resp, 200)
+      resp = get(build_conn(), "/api/v1/accounts/#{user_three.id}/statuses") |> json_response(200)
+      assert [%{"id" => id}] = resp
       assert id == repeat.id
+      assert_schema(resp, "StatusesResponse", ApiSpec.spec())
 
       # When viewing a third user's timeline, the blocked users' statuses will NOT be shown
       resp = get(conn, "/api/v1/accounts/#{user_three.id}/statuses")
@@ -286,30 +289,34 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
       {:ok, private_activity} =
         CommonAPI.post(user_one, %{"status" => "private", "visibility" => "private"})
 
-      resp = get(conn, "/api/v1/accounts/#{user_one.id}/statuses")
-
-      assert [%{"id" => id}] = json_response(resp, 200)
+      resp = get(conn, "/api/v1/accounts/#{user_one.id}/statuses") |> json_response(200)
+      assert [%{"id" => id}] = resp
       assert id == to_string(activity.id)
+      assert_schema(resp, "StatusesResponse", ApiSpec.spec())
 
       resp =
         conn
         |> assign(:user, user_two)
         |> assign(:token, insert(:oauth_token, user: user_two, scopes: ["read:statuses"]))
         |> get("/api/v1/accounts/#{user_one.id}/statuses")
+        |> json_response(200)
 
-      assert [%{"id" => id_one}, %{"id" => id_two}] = json_response(resp, 200)
+      assert [%{"id" => id_one}, %{"id" => id_two}] = resp
       assert id_one == to_string(direct_activity.id)
       assert id_two == to_string(activity.id)
+      assert_schema(resp, "StatusesResponse", ApiSpec.spec())
 
       resp =
         conn
         |> assign(:user, user_three)
         |> assign(:token, insert(:oauth_token, user: user_three, scopes: ["read:statuses"]))
         |> get("/api/v1/accounts/#{user_one.id}/statuses")
+        |> json_response(200)
 
-      assert [%{"id" => id_one}, %{"id" => id_two}] = json_response(resp, 200)
+      assert [%{"id" => id_one}, %{"id" => id_two}] = resp
       assert id_one == to_string(private_activity.id)
       assert id_two == to_string(activity.id)
+      assert_schema(resp, "StatusesResponse", ApiSpec.spec())
     end
 
     test "unimplemented pinned statuses feature", %{conn: conn} do
@@ -335,40 +342,45 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
 
       {:ok, image_post} = CommonAPI.post(user, %{"status" => "cofe", "media_ids" => [media_id]})
 
-      conn = get(conn, "/api/v1/accounts/#{user.id}/statuses", %{"only_media" => "true"})
+      conn = get(conn, "/api/v1/accounts/#{user.id}/statuses?only_media=true")
 
       assert [%{"id" => id}] = json_response(conn, 200)
       assert id == to_string(image_post.id)
+      assert_schema(json_response(conn, 200), "StatusesResponse", ApiSpec.spec())
 
-      conn = get(build_conn(), "/api/v1/accounts/#{user.id}/statuses", %{"only_media" => "1"})
+      conn = get(build_conn(), "/api/v1/accounts/#{user.id}/statuses?only_media=1")
 
       assert [%{"id" => id}] = json_response(conn, 200)
       assert id == to_string(image_post.id)
+      assert_schema(json_response(conn, 200), "StatusesResponse", ApiSpec.spec())
     end
 
     test "gets a user's statuses without reblogs", %{user: user, conn: conn} do
       {:ok, post} = CommonAPI.post(user, %{"status" => "HI!!!"})
       {:ok, _, _} = CommonAPI.repeat(post.id, user)
 
-      conn = get(conn, "/api/v1/accounts/#{user.id}/statuses", %{"exclude_reblogs" => "true"})
+      conn = get(conn, "/api/v1/accounts/#{user.id}/statuses?exclude_reblogs=true")
 
       assert [%{"id" => id}] = json_response(conn, 200)
       assert id == to_string(post.id)
+      assert_schema(json_response(conn, 200), "StatusesResponse", ApiSpec.spec())
 
-      conn = get(conn, "/api/v1/accounts/#{user.id}/statuses", %{"exclude_reblogs" => "1"})
+      conn = get(conn, "/api/v1/accounts/#{user.id}/statuses?exclude_reblogs=1")
 
       assert [%{"id" => id}] = json_response(conn, 200)
       assert id == to_string(post.id)
+      assert_schema(json_response(conn, 200), "StatusesResponse", ApiSpec.spec())
     end
 
     test "filters user's statuses by a hashtag", %{user: user, conn: conn} do
       {:ok, post} = CommonAPI.post(user, %{"status" => "#hashtag"})
       {:ok, _post} = CommonAPI.post(user, %{"status" => "hashtag"})
 
-      conn = get(conn, "/api/v1/accounts/#{user.id}/statuses", %{"tagged" => "hashtag"})
+      conn = get(conn, "/api/v1/accounts/#{user.id}/statuses?tagged=hashtag")
 
       assert [%{"id" => id}] = json_response(conn, 200)
       assert id == to_string(post.id)
+      assert_schema(json_response(conn, 200), "StatusesResponse", ApiSpec.spec())
     end
 
     test "the user views their own timelines and excludes direct messages", %{
@@ -378,11 +390,11 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
       {:ok, public_activity} = CommonAPI.post(user, %{"status" => ".", "visibility" => "public"})
       {:ok, _direct_activity} = CommonAPI.post(user, %{"status" => ".", "visibility" => "direct"})
 
-      conn =
-        get(conn, "/api/v1/accounts/#{user.id}/statuses", %{"exclude_visibilities" => ["direct"]})
+      conn = get(conn, "/api/v1/accounts/#{user.id}/statuses?exclude_visibilities[]=direct")
 
       assert [%{"id" => id}] = json_response(conn, 200)
       assert id == to_string(public_activity.id)
+      assert_schema(json_response(conn, 200), "StatusesResponse", ApiSpec.spec())
     end
   end
 
@@ -420,9 +432,11 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
 
       res_conn = get(conn, "/api/v1/accounts/#{local.id}/statuses")
       assert length(json_response(res_conn, 200)) == 1
+      assert_schema(json_response(res_conn, 200), "StatusesResponse", ApiSpec.spec())
 
       res_conn = get(conn, "/api/v1/accounts/#{remote.id}/statuses")
       assert length(json_response(res_conn, 200)) == 1
+      assert_schema(json_response(res_conn, 200), "StatusesResponse", ApiSpec.spec())
     end
   end
 
@@ -441,6 +455,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
 
       res_conn = get(conn, "/api/v1/accounts/#{remote.id}/statuses")
       assert length(json_response(res_conn, 200)) == 1
+      assert_schema(json_response(res_conn, 200), "StatusesResponse", ApiSpec.spec())
     end
 
     test "if user is authenticated", %{local: local, remote: remote} do
@@ -448,9 +463,11 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
 
       res_conn = get(conn, "/api/v1/accounts/#{local.id}/statuses")
       assert length(json_response(res_conn, 200)) == 1
+      assert_schema(json_response(res_conn, 200), "StatusesResponse", ApiSpec.spec())
 
       res_conn = get(conn, "/api/v1/accounts/#{remote.id}/statuses")
       assert length(json_response(res_conn, 200)) == 1
+      assert_schema(json_response(res_conn, 200), "StatusesResponse", ApiSpec.spec())
     end
   end
 
@@ -463,6 +480,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
     test "if user is unauthenticated", %{conn: conn, local: local, remote: remote} do
       res_conn = get(conn, "/api/v1/accounts/#{local.id}/statuses")
       assert length(json_response(res_conn, 200)) == 1
+      assert_schema(json_response(res_conn, 200), "StatusesResponse", ApiSpec.spec())
 
       res_conn = get(conn, "/api/v1/accounts/#{remote.id}/statuses")
 
@@ -476,9 +494,11 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
 
       res_conn = get(conn, "/api/v1/accounts/#{local.id}/statuses")
       assert length(json_response(res_conn, 200)) == 1
+      assert_schema(json_response(res_conn, 200), "StatusesResponse", ApiSpec.spec())
 
       res_conn = get(conn, "/api/v1/accounts/#{remote.id}/statuses")
       assert length(json_response(res_conn, 200)) == 1
+      assert_schema(json_response(res_conn, 200), "StatusesResponse", ApiSpec.spec())
     end
   end
 

From bd6e2b300f82e66afb121c2339c3cbbfb0b1a446 Mon Sep 17 00:00:00 2001
From: Egor Kislitsyn <egor@kislitsyn.com>
Date: Wed, 8 Apr 2020 23:16:20 +0400
Subject: [PATCH 08/40] Add spec for AccountController.followers

---
 .../api_spec/operations/account_operation.ex  | 19 ++++++++++++++++++-
 .../web/api_spec/schemas/accounts_response.ex | 13 +++++++++++++
 .../controllers/account_controller.ex         |  8 +++++++-
 .../controllers/account_controller_test.exs   |  4 ++++
 4 files changed, 42 insertions(+), 2 deletions(-)
 create mode 100644 lib/pleroma/web/api_spec/schemas/accounts_response.ex

diff --git a/lib/pleroma/web/api_spec/operations/account_operation.ex b/lib/pleroma/web/api_spec/operations/account_operation.ex
index 09e6d24ed..070c74758 100644
--- a/lib/pleroma/web/api_spec/operations/account_operation.ex
+++ b/lib/pleroma/web/api_spec/operations/account_operation.ex
@@ -11,6 +11,7 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
   alias Pleroma.Web.ApiSpec.Schemas.AccountCreateRequest
   alias Pleroma.Web.ApiSpec.Schemas.AccountCreateResponse
   alias Pleroma.Web.ApiSpec.Schemas.AccountRelationshipsResponse
+  alias Pleroma.Web.ApiSpec.Schemas.AccountsResponse
   alias Pleroma.Web.ApiSpec.Schemas.AccountUpdateCredentialsRequest
   alias Pleroma.Web.ApiSpec.Schemas.BooleanLike
   alias Pleroma.Web.ApiSpec.Schemas.StatusesResponse
@@ -139,7 +140,23 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
   end
 
   def followers_operation do
-    :ok
+    %Operation{
+      tags: ["accounts"],
+      summary: "Followers",
+      operationId: "AccountController.followers",
+      security: [%{"oAuth" => ["read:accounts"]}],
+      description:
+        "Accounts which follow the given account, if network is not hidden by the account owner.",
+      parameters: [
+        %Reference{"$ref": "#/components/parameters/accountIdOrNickname"},
+        Operation.parameter(:max_id, :query, :string, "Max ID"),
+        Operation.parameter(:since_id, :query, :string, "Since ID"),
+        Operation.parameter(:limit, :query, :integer, "Limit")
+      ],
+      responses: %{
+        200 => Operation.response("Accounts", "application/json", AccountsResponse)
+      }
+    }
   end
 
   def following_operation, do: :ok
diff --git a/lib/pleroma/web/api_spec/schemas/accounts_response.ex b/lib/pleroma/web/api_spec/schemas/accounts_response.ex
new file mode 100644
index 000000000..b714f59e7
--- /dev/null
+++ b/lib/pleroma/web/api_spec/schemas/accounts_response.ex
@@ -0,0 +1,13 @@
+# 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.AccountsResponse do
+  require OpenApiSpex
+
+  OpenApiSpex.schema(%{
+    title: "AccountsResponse",
+    type: :array,
+    items: Pleroma.Web.ApiSpec.Schemas.Account
+  })
+end
diff --git a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
index 208df5698..1ffccdd1d 100644
--- a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
@@ -89,7 +89,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
            :update_credentials,
            :relationships,
            :show,
-           :statuses
+           :statuses,
+           :followers
          ]
   )
 
@@ -284,6 +285,11 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
 
   @doc "GET /api/v1/accounts/:id/followers"
   def followers(%{assigns: %{user: for_user, account: user}} = conn, params) do
+    params =
+      params
+      |> Enum.map(fn {key, value} -> {to_string(key), value} end)
+      |> Enum.into(%{})
+
     followers =
       cond do
         for_user && user.id == for_user.id -> MastodonAPI.get_followers(user, params)
diff --git a/test/web/mastodon_api/controllers/account_controller_test.exs b/test/web/mastodon_api/controllers/account_controller_test.exs
index 969256fa4..79b3adc69 100644
--- a/test/web/mastodon_api/controllers/account_controller_test.exs
+++ b/test/web/mastodon_api/controllers/account_controller_test.exs
@@ -513,6 +513,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
 
       assert [%{"id" => id}] = json_response(conn, 200)
       assert id == to_string(user.id)
+      assert_schema(json_response(conn, 200), "AccountsResponse", ApiSpec.spec())
     end
 
     test "getting followers, hide_followers", %{user: user, conn: conn} do
@@ -536,6 +537,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
         |> get("/api/v1/accounts/#{other_user.id}/followers")
 
       refute [] == json_response(conn, 200)
+      assert_schema(json_response(conn, 200), "AccountsResponse", ApiSpec.spec())
     end
 
     test "getting followers, pagination", %{user: user, conn: conn} do
@@ -551,6 +553,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
       assert [%{"id" => id3}, %{"id" => id2}] = json_response(res_conn, 200)
       assert id3 == follower3.id
       assert id2 == follower2.id
+      assert_schema(json_response(res_conn, 200), "AccountsResponse", ApiSpec.spec())
 
       res_conn = get(conn, "/api/v1/accounts/#{user.id}/followers?max_id=#{follower3.id}")
 
@@ -566,6 +569,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
       assert [link_header] = get_resp_header(res_conn, "link")
       assert link_header =~ ~r/min_id=#{follower2.id}/
       assert link_header =~ ~r/max_id=#{follower2.id}/
+      assert_schema(json_response(res_conn, 200), "AccountsResponse", ApiSpec.spec())
     end
   end
 

From e105cc12b67e44eb4e19293b850731f300999a4f Mon Sep 17 00:00:00 2001
From: Egor Kislitsyn <egor@kislitsyn.com>
Date: Wed, 8 Apr 2020 23:38:07 +0400
Subject: [PATCH 09/40] Add spec for AccountController.following

---
 .../api_spec/operations/account_operation.ex  | 35 +++++++++++++++++--
 .../controllers/account_controller.ex         |  8 ++++-
 .../controllers/account_controller_test.exs   |  5 +++
 3 files changed, 45 insertions(+), 3 deletions(-)

diff --git a/lib/pleroma/web/api_spec/operations/account_operation.ex b/lib/pleroma/web/api_spec/operations/account_operation.ex
index 070c74758..456d08a45 100644
--- a/lib/pleroma/web/api_spec/operations/account_operation.ex
+++ b/lib/pleroma/web/api_spec/operations/account_operation.ex
@@ -150,8 +150,40 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
       parameters: [
         %Reference{"$ref": "#/components/parameters/accountIdOrNickname"},
         Operation.parameter(:max_id, :query, :string, "Max ID"),
+        Operation.parameter(:min_id, :query, :string, "Mix ID"),
         Operation.parameter(:since_id, :query, :string, "Since ID"),
-        Operation.parameter(:limit, :query, :integer, "Limit")
+        Operation.parameter(
+          :limit,
+          :query,
+          %Schema{type: :integer, default: 20, maximum: 40},
+          "Limit"
+        )
+      ],
+      responses: %{
+        200 => Operation.response("Accounts", "application/json", AccountsResponse)
+      }
+    }
+  end
+
+  def following_operation do
+    %Operation{
+      tags: ["accounts"],
+      summary: "Following",
+      operationId: "AccountController.following",
+      security: [%{"oAuth" => ["read:accounts"]}],
+      description:
+        "Accounts which the given account is following, if network is not hidden by the account owner.",
+      parameters: [
+        %Reference{"$ref": "#/components/parameters/accountIdOrNickname"},
+        Operation.parameter(:max_id, :query, :string, "Max ID"),
+        Operation.parameter(:min_id, :query, :string, "Mix ID"),
+        Operation.parameter(:since_id, :query, :string, "Since ID"),
+        Operation.parameter(
+          :limit,
+          :query,
+          %Schema{type: :integer, default: 20, maximum: 40},
+          "Limit"
+        )
       ],
       responses: %{
         200 => Operation.response("Accounts", "application/json", AccountsResponse)
@@ -159,7 +191,6 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
     }
   end
 
-  def following_operation, do: :ok
   def lists_operation, do: :ok
   def follow_operation, do: :ok
   def unfollow_operation, do: :ok
diff --git a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
index 1ffccdd1d..e74180662 100644
--- a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
@@ -90,7 +90,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
            :relationships,
            :show,
            :statuses,
-           :followers
+           :followers,
+           :following
          ]
   )
 
@@ -304,6 +305,11 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
 
   @doc "GET /api/v1/accounts/:id/following"
   def following(%{assigns: %{user: for_user, account: user}} = conn, params) do
+    params =
+      params
+      |> Enum.map(fn {key, value} -> {to_string(key), value} end)
+      |> Enum.into(%{})
+
     followers =
       cond do
         for_user && user.id == for_user.id -> MastodonAPI.get_friends(user, params)
diff --git a/test/web/mastodon_api/controllers/account_controller_test.exs b/test/web/mastodon_api/controllers/account_controller_test.exs
index 79b3adc69..341c9b015 100644
--- a/test/web/mastodon_api/controllers/account_controller_test.exs
+++ b/test/web/mastodon_api/controllers/account_controller_test.exs
@@ -584,6 +584,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
 
       assert [%{"id" => id}] = json_response(conn, 200)
       assert id == to_string(other_user.id)
+      assert_schema(json_response(conn, 200), "AccountsResponse", ApiSpec.spec())
     end
 
     test "getting following, hide_follows, other user requesting" do
@@ -598,6 +599,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
         |> get("/api/v1/accounts/#{user.id}/following")
 
       assert [] == json_response(conn, 200)
+      assert_schema(json_response(conn, 200), "AccountsResponse", ApiSpec.spec())
     end
 
     test "getting following, hide_follows, same user requesting" do
@@ -627,12 +629,14 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
       assert [%{"id" => id3}, %{"id" => id2}] = json_response(res_conn, 200)
       assert id3 == following3.id
       assert id2 == following2.id
+      assert_schema(json_response(res_conn, 200), "AccountsResponse", ApiSpec.spec())
 
       res_conn = get(conn, "/api/v1/accounts/#{user.id}/following?max_id=#{following3.id}")
 
       assert [%{"id" => id2}, %{"id" => id1}] = json_response(res_conn, 200)
       assert id2 == following2.id
       assert id1 == following1.id
+      assert_schema(json_response(res_conn, 200), "AccountsResponse", ApiSpec.spec())
 
       res_conn =
         get(conn, "/api/v1/accounts/#{user.id}/following?limit=1&max_id=#{following3.id}")
@@ -643,6 +647,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
       assert [link_header] = get_resp_header(res_conn, "link")
       assert link_header =~ ~r/min_id=#{following2.id}/
       assert link_header =~ ~r/max_id=#{following2.id}/
+      assert_schema(json_response(res_conn, 200), "AccountsResponse", ApiSpec.spec())
     end
   end
 

From 1b680a98ae15035215fa8489f825af72532340c4 Mon Sep 17 00:00:00 2001
From: Egor Kislitsyn <egor@kislitsyn.com>
Date: Wed, 8 Apr 2020 23:51:46 +0400
Subject: [PATCH 10/40] Add spec for AccountController.lists

---
 .../api_spec/operations/account_operation.ex  | 18 ++++++++++++-
 lib/pleroma/web/api_spec/schemas/list.ex      | 25 +++++++++++++++++++
 .../web/api_spec/schemas/lists_response.ex    | 16 ++++++++++++
 .../controllers/account_controller.ex         |  3 ++-
 .../controllers/account_controller_test.exs   |  1 +
 5 files changed, 61 insertions(+), 2 deletions(-)
 create mode 100644 lib/pleroma/web/api_spec/schemas/list.ex
 create mode 100644 lib/pleroma/web/api_spec/schemas/lists_response.ex

diff --git a/lib/pleroma/web/api_spec/operations/account_operation.ex b/lib/pleroma/web/api_spec/operations/account_operation.ex
index 456d08a45..ad10f4ec9 100644
--- a/lib/pleroma/web/api_spec/operations/account_operation.ex
+++ b/lib/pleroma/web/api_spec/operations/account_operation.ex
@@ -14,6 +14,7 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
   alias Pleroma.Web.ApiSpec.Schemas.AccountsResponse
   alias Pleroma.Web.ApiSpec.Schemas.AccountUpdateCredentialsRequest
   alias Pleroma.Web.ApiSpec.Schemas.BooleanLike
+  alias Pleroma.Web.ApiSpec.Schemas.ListsResponse
   alias Pleroma.Web.ApiSpec.Schemas.StatusesResponse
   alias Pleroma.Web.ApiSpec.Schemas.VisibilityScope
 
@@ -191,7 +192,22 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
     }
   end
 
-  def lists_operation, do: :ok
+  def lists_operation do
+    %Operation{
+      tags: ["accounts"],
+      summary: "Lists containing this account",
+      operationId: "AccountController.lists",
+      security: [%{"oAuth" => ["read:lists"]}],
+      description: "User lists that you have added this account to.",
+      parameters: [
+        %Reference{"$ref": "#/components/parameters/accountIdOrNickname"}
+      ],
+      responses: %{
+        200 => Operation.response("Lists", "application/json", ListsResponse)
+      }
+    }
+  end
+
   def follow_operation, do: :ok
   def unfollow_operation, do: :ok
   def mute_operation, do: :ok
diff --git a/lib/pleroma/web/api_spec/schemas/list.ex b/lib/pleroma/web/api_spec/schemas/list.ex
new file mode 100644
index 000000000..30fa7db93
--- /dev/null
+++ b/lib/pleroma/web/api_spec/schemas/list.ex
@@ -0,0 +1,25 @@
+# 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.List do
+  alias OpenApiSpex.Schema
+
+  require OpenApiSpex
+
+  OpenApiSpex.schema(%{
+    title: "List",
+    description: "Response schema for a list",
+    type: :object,
+    properties: %{
+      id: %Schema{type: :string},
+      title: %Schema{type: :string}
+    },
+    example: %{
+      "JSON" => %{
+        "id" => "123",
+        "title" => "my list"
+      }
+    }
+  })
+end
diff --git a/lib/pleroma/web/api_spec/schemas/lists_response.ex b/lib/pleroma/web/api_spec/schemas/lists_response.ex
new file mode 100644
index 000000000..132454579
--- /dev/null
+++ b/lib/pleroma/web/api_spec/schemas/lists_response.ex
@@ -0,0 +1,16 @@
+# 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.ListsResponse do
+  alias Pleroma.Web.ApiSpec.Schemas.List
+
+  require OpenApiSpex
+
+  OpenApiSpex.schema(%{
+    title: "ListsResponse",
+    description: "Response schema for lists",
+    type: :array,
+    items: List
+  })
+end
diff --git a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
index e74180662..2c5cd8cde 100644
--- a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
@@ -91,7 +91,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
            :show,
            :statuses,
            :followers,
-           :following
+           :following,
+           :lists
          ]
   )
 
diff --git a/test/web/mastodon_api/controllers/account_controller_test.exs b/test/web/mastodon_api/controllers/account_controller_test.exs
index 341c9b015..706eea5d9 100644
--- a/test/web/mastodon_api/controllers/account_controller_test.exs
+++ b/test/web/mastodon_api/controllers/account_controller_test.exs
@@ -1051,6 +1051,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
         |> json_response(200)
 
       assert res == [%{"id" => to_string(list.id), "title" => "Test List"}]
+      assert_schema(res, "ListsResponse", ApiSpec.spec())
     end
   end
 

From 854780c72bc90a55d7005a05861d45771c5aeadf Mon Sep 17 00:00:00 2001
From: Egor Kislitsyn <egor@kislitsyn.com>
Date: Thu, 9 Apr 2020 15:25:24 +0400
Subject: [PATCH 11/40] Add spec for AccountController.follow

---
 lib/pleroma/web/api_spec.ex                   |  2 +-
 .../api_spec/operations/account_operation.ex  | 28 +++++++++++----
 ...ip_response.ex => account_relationship.ex} | 36 ++++++++++---------
 .../schemas/account_relationships_response.ex |  5 ++-
 .../controllers/account_controller.ex         |  7 ++--
 .../controllers/account_controller_test.exs   |  1 +
 6 files changed, 51 insertions(+), 28 deletions(-)
 rename lib/pleroma/web/api_spec/schemas/{account_relationship_response.ex => account_relationship.ex} (71%)

diff --git a/lib/pleroma/web/api_spec.ex b/lib/pleroma/web/api_spec.ex
index d11e776d0..b3c1e3ea2 100644
--- a/lib/pleroma/web/api_spec.ex
+++ b/lib/pleroma/web/api_spec.ex
@@ -39,7 +39,7 @@ defmodule Pleroma.Web.ApiSpec do
               password: %OpenApiSpex.OAuthFlow{
                 authorizationUrl: "/oauth/authorize",
                 tokenUrl: "/oauth/token",
-                scopes: %{"read" => "read", "write" => "write"}
+                scopes: %{"read" => "read", "write" => "write", "follow" => "follow"}
               }
             }
           }
diff --git a/lib/pleroma/web/api_spec/operations/account_operation.ex b/lib/pleroma/web/api_spec/operations/account_operation.ex
index ad10f4ec9..a76141f7a 100644
--- a/lib/pleroma/web/api_spec/operations/account_operation.ex
+++ b/lib/pleroma/web/api_spec/operations/account_operation.ex
@@ -10,6 +10,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.AccountRelationship
   alias Pleroma.Web.ApiSpec.Schemas.AccountRelationshipsResponse
   alias Pleroma.Web.ApiSpec.Schemas.AccountsResponse
   alias Pleroma.Web.ApiSpec.Schemas.AccountUpdateCredentialsRequest
@@ -186,9 +187,7 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
           "Limit"
         )
       ],
-      responses: %{
-        200 => Operation.response("Accounts", "application/json", AccountsResponse)
-      }
+      responses: %{200 => Operation.response("Accounts", "application/json", AccountsResponse)}
     }
   end
 
@@ -199,16 +198,33 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
       operationId: "AccountController.lists",
       security: [%{"oAuth" => ["read:lists"]}],
       description: "User lists that you have added this account to.",
+      parameters: [%Reference{"$ref": "#/components/parameters/accountIdOrNickname"}],
+      responses: %{200 => Operation.response("Lists", "application/json", ListsResponse)}
+    }
+  end
+
+  def follow_operation do
+    %Operation{
+      tags: ["accounts"],
+      summary: "Follow",
+      operationId: "AccountController.follow",
+      security: [%{"oAuth" => ["follow", "write:follows"]}],
+      description: "Follow the given account",
       parameters: [
-        %Reference{"$ref": "#/components/parameters/accountIdOrNickname"}
+        %Reference{"$ref": "#/components/parameters/accountIdOrNickname"},
+        Operation.parameter(
+          :reblogs,
+          :query,
+          BooleanLike,
+          "Receive this account's reblogs in home timeline? Defaults to true."
+        )
       ],
       responses: %{
-        200 => Operation.response("Lists", "application/json", ListsResponse)
+        200 => Operation.response("Relationship", "application/json", AccountRelationship)
       }
     }
   end
 
-  def follow_operation, do: :ok
   def unfollow_operation, do: :ok
   def mute_operation, do: :ok
   def unmute_operation, do: :ok
diff --git a/lib/pleroma/web/api_spec/schemas/account_relationship_response.ex b/lib/pleroma/web/api_spec/schemas/account_relationship.ex
similarity index 71%
rename from lib/pleroma/web/api_spec/schemas/account_relationship_response.ex
rename to lib/pleroma/web/api_spec/schemas/account_relationship.ex
index 9974b946b..7db3b49bb 100644
--- a/lib/pleroma/web/api_spec/schemas/account_relationship_response.ex
+++ b/lib/pleroma/web/api_spec/schemas/account_relationship.ex
@@ -2,41 +2,43 @@
 # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
 # SPDX-License-Identifier: AGPL-3.0-only
 
-defmodule Pleroma.Web.ApiSpec.Schemas.AccountRelationshipResponse do
+defmodule Pleroma.Web.ApiSpec.Schemas.AccountRelationship do
   alias OpenApiSpex.Schema
 
   require OpenApiSpex
 
   OpenApiSpex.schema(%{
-    title: "AccountRelationshipResponse",
-    description: "Response schema for an account relationship",
+    title: "AccountRelationship",
+    description: "Response schema for relationship",
     type: :object,
     properties: %{
-      id: %Schema{type: :string},
-      following: %Schema{type: :boolean},
-      showing_reblogs: %Schema{type: :boolean},
-      followed_by: %Schema{type: :boolean},
-      blocking: %Schema{type: :boolean},
       blocked_by: %Schema{type: :boolean},
+      blocking: %Schema{type: :boolean},
+      domain_blocking: %Schema{type: :boolean},
+      endorsed: %Schema{type: :boolean},
+      followed_by: %Schema{type: :boolean},
+      following: %Schema{type: :boolean},
+      id: %Schema{type: :string},
       muting: %Schema{type: :boolean},
       muting_notifications: %Schema{type: :boolean},
       requested: %Schema{type: :boolean},
-      domain_blocking: %Schema{type: :boolean},
-      endorsed: %Schema{type: :boolean}
+      showing_reblogs: %Schema{type: :boolean},
+      subscribing: %Schema{type: :boolean}
     },
     example: %{
       "JSON" => %{
-        "id" => "1",
-        "following" => true,
-        "showing_reblogs" => true,
-        "followed_by" => true,
-        "blocking" => false,
         "blocked_by" => false,
+        "blocking" => false,
+        "domain_blocking" => false,
+        "endorsed" => false,
+        "followed_by" => false,
+        "following" => false,
+        "id" => "9tKi3esbG7OQgZ2920",
         "muting" => false,
         "muting_notifications" => false,
         "requested" => false,
-        "domain_blocking" => false,
-        "endorsed" => false
+        "showing_reblogs" => true,
+        "subscribing" => false
       }
     }
   })
diff --git a/lib/pleroma/web/api_spec/schemas/account_relationships_response.ex b/lib/pleroma/web/api_spec/schemas/account_relationships_response.ex
index 2ca632310..960e14db1 100644
--- a/lib/pleroma/web/api_spec/schemas/account_relationships_response.ex
+++ b/lib/pleroma/web/api_spec/schemas/account_relationships_response.ex
@@ -9,7 +9,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.AccountRelationshipsResponse do
     title: "AccountRelationshipsResponse",
     description: "Response schema for account relationships",
     type: :array,
-    items: Pleroma.Web.ApiSpec.Schemas.AccountRelationshipResponse,
+    items: Pleroma.Web.ApiSpec.Schemas.AccountRelationship,
     example: [
       %{
         "id" => "1",
@@ -22,6 +22,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.AccountRelationshipsResponse do
         "muting_notifications" => false,
         "requested" => false,
         "domain_blocking" => false,
+        "subscribing" => false,
         "endorsed" => true
       },
       %{
@@ -35,6 +36,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.AccountRelationshipsResponse do
         "muting_notifications" => false,
         "requested" => true,
         "domain_blocking" => false,
+        "subscribing" => false,
         "endorsed" => false
       },
       %{
@@ -48,6 +50,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.AccountRelationshipsResponse do
         "muting_notifications" => false,
         "requested" => false,
         "domain_blocking" => true,
+        "subscribing" => true,
         "endorsed" => false
       }
     ]
diff --git a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
index 2c5cd8cde..d2ad65ef3 100644
--- a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
@@ -92,7 +92,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
            :statuses,
            :followers,
            :following,
-           :lists
+           :lists,
+           :follow
          ]
   )
 
@@ -337,8 +338,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
     {:error, :not_found}
   end
 
-  def follow(%{assigns: %{user: follower, account: followed}} = conn, _params) do
-    with {:ok, follower} <- MastodonAPI.follow(follower, followed, conn.params) do
+  def follow(%{assigns: %{user: follower, account: followed}} = conn, params) do
+    with {:ok, follower} <- MastodonAPI.follow(follower, followed, params) do
       render(conn, "relationship.json", user: follower, target: followed)
     else
       {:error, message} -> json_response(conn, :forbidden, %{error: message})
diff --git a/test/web/mastodon_api/controllers/account_controller_test.exs b/test/web/mastodon_api/controllers/account_controller_test.exs
index 706eea5d9..7a3d58600 100644
--- a/test/web/mastodon_api/controllers/account_controller_test.exs
+++ b/test/web/mastodon_api/controllers/account_controller_test.exs
@@ -669,6 +669,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
 
       assert %{"id" => id} = json_response(conn, 200)
       assert id == to_string(other_user.id)
+      assert_schema(json_response(conn, 200), "AccountRelationship", ApiSpec.spec())
     end
 
     test "cancelling follow request", %{conn: conn} do

From aa958a6dda7cdcf12e9cd9232e7c6be421610317 Mon Sep 17 00:00:00 2001
From: Egor Kislitsyn <egor@kislitsyn.com>
Date: Thu, 9 Apr 2020 17:57:21 +0400
Subject: [PATCH 12/40] Add spec for AccountController.unfollow

---
 .../web/api_spec/operations/account_operation.ex | 15 ++++++++++++++-
 .../controllers/account_controller.ex            |  3 ++-
 .../controllers/account_controller_test.exs      | 16 ++++++++++++----
 3 files changed, 28 insertions(+), 6 deletions(-)

diff --git a/lib/pleroma/web/api_spec/operations/account_operation.ex b/lib/pleroma/web/api_spec/operations/account_operation.ex
index a76141f7a..8925ebefd 100644
--- a/lib/pleroma/web/api_spec/operations/account_operation.ex
+++ b/lib/pleroma/web/api_spec/operations/account_operation.ex
@@ -225,7 +225,20 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
     }
   end
 
-  def unfollow_operation, do: :ok
+  def unfollow_operation do
+    %Operation{
+      tags: ["accounts"],
+      summary: "Unfollow",
+      operationId: "AccountController.unfollow",
+      security: [%{"oAuth" => ["follow", "write:follows"]}],
+      description: "Unfollow the given account",
+      parameters: [%Reference{"$ref": "#/components/parameters/accountIdOrNickname"}],
+      responses: %{
+        200 => Operation.response("Relationship", "application/json", AccountRelationship)
+      }
+    }
+  end
+
   def mute_operation, do: :ok
   def unmute_operation, do: :ok
   def block_operation, do: :ok
diff --git a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
index d2ad65ef3..1ecce2928 100644
--- a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
@@ -93,7 +93,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
            :followers,
            :following,
            :lists,
-           :follow
+           :follow,
+           :unfollow
          ]
   )
 
diff --git a/test/web/mastodon_api/controllers/account_controller_test.exs b/test/web/mastodon_api/controllers/account_controller_test.exs
index 7a3d58600..d56e7fb4a 100644
--- a/test/web/mastodon_api/controllers/account_controller_test.exs
+++ b/test/web/mastodon_api/controllers/account_controller_test.exs
@@ -660,10 +660,12 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
       ret_conn = post(conn, "/api/v1/accounts/#{other_user.id}/follow")
 
       assert %{"id" => _id, "following" => true} = json_response(ret_conn, 200)
+      assert_schema(json_response(ret_conn, 200), "AccountRelationship", ApiSpec.spec())
 
       ret_conn = post(conn, "/api/v1/accounts/#{other_user.id}/unfollow")
 
       assert %{"id" => _id, "following" => false} = json_response(ret_conn, 200)
+      assert_schema(json_response(ret_conn, 200), "AccountRelationship", ApiSpec.spec())
 
       conn = post(conn, "/api/v1/follows", %{"uri" => other_user.nickname})
 
@@ -675,11 +677,15 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
     test "cancelling follow request", %{conn: conn} do
       %{id: other_user_id} = insert(:user, %{locked: true})
 
-      assert %{"id" => ^other_user_id, "following" => false, "requested" => true} =
-               conn |> post("/api/v1/accounts/#{other_user_id}/follow") |> json_response(:ok)
+      resp = conn |> post("/api/v1/accounts/#{other_user_id}/follow") |> json_response(:ok)
 
-      assert %{"id" => ^other_user_id, "following" => false, "requested" => false} =
-               conn |> post("/api/v1/accounts/#{other_user_id}/unfollow") |> json_response(:ok)
+      assert %{"id" => ^other_user_id, "following" => false, "requested" => true} = resp
+      assert_schema(resp, "AccountRelationship", ApiSpec.spec())
+
+      resp = conn |> post("/api/v1/accounts/#{other_user_id}/unfollow") |> json_response(:ok)
+
+      assert %{"id" => ^other_user_id, "following" => false, "requested" => false} = resp
+      assert_schema(resp, "AccountRelationship", ApiSpec.spec())
     end
 
     test "following without reblogs" do
@@ -690,6 +696,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
       ret_conn = post(conn, "/api/v1/accounts/#{followed.id}/follow?reblogs=false")
 
       assert %{"showing_reblogs" => false} = json_response(ret_conn, 200)
+      assert_schema(json_response(ret_conn, 200), "AccountRelationship", ApiSpec.spec())
 
       {:ok, activity} = CommonAPI.post(other_user, %{"status" => "hey"})
       {:ok, reblog, _} = CommonAPI.repeat(activity.id, followed)
@@ -701,6 +708,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
       ret_conn = post(conn, "/api/v1/accounts/#{followed.id}/follow?reblogs=true")
 
       assert %{"showing_reblogs" => true} = json_response(ret_conn, 200)
+      assert_schema(json_response(ret_conn, 200), "AccountRelationship", ApiSpec.spec())
 
       conn = get(conn, "/api/v1/timelines/home")
 

From e4195d4a684908d58482f9c865375a080e7b78bc Mon Sep 17 00:00:00 2001
From: Egor Kislitsyn <egor@kislitsyn.com>
Date: Thu, 9 Apr 2020 18:28:14 +0400
Subject: [PATCH 13/40] Add specs for AccountController.mute and
 AccountController.unmute

---
 .../api_spec/operations/account_operation.ex  | 41 ++++++++++++++++++-
 .../api_spec/schemas/account_mute_request.ex  | 24 +++++++++++
 .../controllers/account_controller.ex         | 10 ++---
 .../controllers/account_controller_test.exs   | 13 +++++-
 4 files changed, 79 insertions(+), 9 deletions(-)
 create mode 100644 lib/pleroma/web/api_spec/schemas/account_mute_request.ex

diff --git a/lib/pleroma/web/api_spec/operations/account_operation.ex b/lib/pleroma/web/api_spec/operations/account_operation.ex
index 8925ebefd..62ae2eead 100644
--- a/lib/pleroma/web/api_spec/operations/account_operation.ex
+++ b/lib/pleroma/web/api_spec/operations/account_operation.ex
@@ -10,6 +10,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.AccountMuteRequest
   alias Pleroma.Web.ApiSpec.Schemas.AccountRelationship
   alias Pleroma.Web.ApiSpec.Schemas.AccountRelationshipsResponse
   alias Pleroma.Web.ApiSpec.Schemas.AccountsResponse
@@ -239,8 +240,44 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
     }
   end
 
-  def mute_operation, do: :ok
-  def unmute_operation, do: :ok
+  def mute_operation do
+    %Operation{
+      tags: ["accounts"],
+      summary: "Mute",
+      operationId: "AccountController.mute",
+      security: [%{"oAuth" => ["follow", "write:mutes"]}],
+      requestBody: Helpers.request_body("Parameters", AccountMuteRequest),
+      description:
+        "Mute the given account. Clients should filter statuses and notifications from this account, if received (e.g. due to a boost in the Home timeline).",
+      parameters: [
+        %Reference{"$ref": "#/components/parameters/accountIdOrNickname"},
+        Operation.parameter(
+          :notifications,
+          :query,
+          %Schema{allOf: [BooleanLike], default: true},
+          "Mute notifications in addition to statuses? Defaults to `true`."
+        )
+      ],
+      responses: %{
+        200 => Operation.response("Relationship", "application/json", AccountRelationship)
+      }
+    }
+  end
+
+  def unmute_operation do
+    %Operation{
+      tags: ["accounts"],
+      summary: "Unmute",
+      operationId: "AccountController.unmute",
+      security: [%{"oAuth" => ["follow", "write:mutes"]}],
+      description: "Unmute the given account.",
+      parameters: [%Reference{"$ref": "#/components/parameters/accountIdOrNickname"}],
+      responses: %{
+        200 => Operation.response("Relationship", "application/json", AccountRelationship)
+      }
+    }
+  end
+
   def block_operation, do: :ok
   def unblock_operation, do: :ok
   def follows_operation, do: :ok
diff --git a/lib/pleroma/web/api_spec/schemas/account_mute_request.ex b/lib/pleroma/web/api_spec/schemas/account_mute_request.ex
new file mode 100644
index 000000000..a61f6d04c
--- /dev/null
+++ b/lib/pleroma/web/api_spec/schemas/account_mute_request.ex
@@ -0,0 +1,24 @@
+# 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.AccountMuteRequest do
+  alias OpenApiSpex.Schema
+  require OpenApiSpex
+
+  OpenApiSpex.schema(%{
+    title: "AccountMuteRequest",
+    description: "POST body for muting an account",
+    type: :object,
+    properties: %{
+      notifications: %Schema{
+        type: :boolean,
+        description: "Mute notifications in addition to statuses? Defaults to true.",
+        default: true
+      }
+    },
+    example: %{
+      "notifications" => true
+    }
+  })
+end
diff --git a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
index 1ecce2928..9aba2e094 100644
--- a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
@@ -94,7 +94,9 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
            :following,
            :lists,
            :follow,
-           :unfollow
+           :unfollow,
+           :mute,
+           :unmute
          ]
   )
 
@@ -359,10 +361,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
   end
 
   @doc "POST /api/v1/accounts/:id/mute"
-  def mute(%{assigns: %{user: muter, account: muted}} = conn, params) do
-    notifications? = params |> Map.get("notifications", true) |> truthy_param?()
-
-    with {:ok, _user_relationships} <- User.mute(muter, muted, notifications?) do
+  def mute(%{assigns: %{user: muter, account: muted}, body_params: params} = conn, _params) do
+    with {:ok, _user_relationships} <- User.mute(muter, muted, params.notifications) do
       render(conn, "relationship.json", user: muter, target: muted)
     else
       {:error, message} -> json_response(conn, :forbidden, %{error: message})
diff --git a/test/web/mastodon_api/controllers/account_controller_test.exs b/test/web/mastodon_api/controllers/account_controller_test.exs
index d56e7fb4a..91d4685cb 100644
--- a/test/web/mastodon_api/controllers/account_controller_test.exs
+++ b/test/web/mastodon_api/controllers/account_controller_test.exs
@@ -751,32 +751,41 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
     test "with notifications", %{conn: conn} do
       other_user = insert(:user)
 
-      ret_conn = post(conn, "/api/v1/accounts/#{other_user.id}/mute")
+      ret_conn =
+        conn
+        |> put_req_header("content-type", "application/json")
+        |> post("/api/v1/accounts/#{other_user.id}/mute")
 
       response = json_response(ret_conn, 200)
 
       assert %{"id" => _id, "muting" => true, "muting_notifications" => true} = response
+      assert_schema(response, "AccountRelationship", ApiSpec.spec())
 
       conn = post(conn, "/api/v1/accounts/#{other_user.id}/unmute")
 
       response = json_response(conn, 200)
       assert %{"id" => _id, "muting" => false, "muting_notifications" => false} = response
+      assert_schema(response, "AccountRelationship", ApiSpec.spec())
     end
 
     test "without notifications", %{conn: conn} do
       other_user = insert(:user)
 
       ret_conn =
-        post(conn, "/api/v1/accounts/#{other_user.id}/mute", %{"notifications" => "false"})
+        conn
+        |> put_req_header("content-type", "multipart/form-data")
+        |> post("/api/v1/accounts/#{other_user.id}/mute", %{"notifications" => "false"})
 
       response = json_response(ret_conn, 200)
 
       assert %{"id" => _id, "muting" => true, "muting_notifications" => false} = response
+      assert_schema(response, "AccountRelationship", ApiSpec.spec())
 
       conn = post(conn, "/api/v1/accounts/#{other_user.id}/unmute")
 
       response = json_response(conn, 200)
       assert %{"id" => _id, "muting" => false, "muting_notifications" => false} = response
+      assert_schema(response, "AccountRelationship", ApiSpec.spec())
     end
   end
 

From 68a979b8243b9a5b685df2c13388a93b9ede1900 Mon Sep 17 00:00:00 2001
From: Egor Kislitsyn <egor@kislitsyn.com>
Date: Thu, 9 Apr 2020 18:41:18 +0400
Subject: [PATCH 14/40] Add specs for AccountController.block and
 AccountController.unblock

---
 .../api_spec/operations/account_operation.ex  | 31 +++++++++++++++++--
 .../controllers/account_controller.ex         |  4 ++-
 .../controllers/account_controller_test.exs   |  2 ++
 3 files changed, 34 insertions(+), 3 deletions(-)

diff --git a/lib/pleroma/web/api_spec/operations/account_operation.ex b/lib/pleroma/web/api_spec/operations/account_operation.ex
index 62ae2eead..73fbe8785 100644
--- a/lib/pleroma/web/api_spec/operations/account_operation.ex
+++ b/lib/pleroma/web/api_spec/operations/account_operation.ex
@@ -278,8 +278,35 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
     }
   end
 
-  def block_operation, do: :ok
-  def unblock_operation, do: :ok
+  def block_operation do
+    %Operation{
+      tags: ["accounts"],
+      summary: "Block",
+      operationId: "AccountController.block",
+      security: [%{"oAuth" => ["follow", "write:blocks"]}],
+      description:
+        "Block the given account. Clients should filter statuses from this account if received (e.g. due to a boost in the Home timeline)",
+      parameters: [%Reference{"$ref": "#/components/parameters/accountIdOrNickname"}],
+      responses: %{
+        200 => Operation.response("Relationship", "application/json", AccountRelationship)
+      }
+    }
+  end
+
+  def unblock_operation do
+    %Operation{
+      tags: ["accounts"],
+      summary: "Unblock",
+      operationId: "AccountController.unblock",
+      security: [%{"oAuth" => ["follow", "write:blocks"]}],
+      description: "Unblock the given account.",
+      parameters: [%Reference{"$ref": "#/components/parameters/accountIdOrNickname"}],
+      responses: %{
+        200 => Operation.response("Relationship", "application/json", AccountRelationship)
+      }
+    }
+  end
+
   def follows_operation, do: :ok
   def mutes_operation, do: :ok
   def blocks_operation, do: :ok
diff --git a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
index 9aba2e094..c1f70f32c 100644
--- a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
@@ -96,7 +96,9 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
            :follow,
            :unfollow,
            :mute,
-           :unmute
+           :unmute,
+           :block,
+           :unblock
          ]
   )
 
diff --git a/test/web/mastodon_api/controllers/account_controller_test.exs b/test/web/mastodon_api/controllers/account_controller_test.exs
index 91d4685cb..f71b54ade 100644
--- a/test/web/mastodon_api/controllers/account_controller_test.exs
+++ b/test/web/mastodon_api/controllers/account_controller_test.exs
@@ -819,10 +819,12 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
     ret_conn = post(conn, "/api/v1/accounts/#{other_user.id}/block")
 
     assert %{"id" => _id, "blocking" => true} = json_response(ret_conn, 200)
+    assert_schema(json_response(ret_conn, 200), "AccountRelationship", ApiSpec.spec())
 
     conn = post(conn, "/api/v1/accounts/#{other_user.id}/unblock")
 
     assert %{"id" => _id, "blocking" => false} = json_response(conn, 200)
+    assert_schema(json_response(ret_conn, 200), "AccountRelationship", ApiSpec.spec())
   end
 
   describe "create account by app" do

From ab185d3ea47deb38128dc501acdf27c47c542de2 Mon Sep 17 00:00:00 2001
From: Egor Kislitsyn <egor@kislitsyn.com>
Date: Thu, 9 Apr 2020 20:12:09 +0400
Subject: [PATCH 15/40] Add spec for AccountController.follows

---
 .../api_spec/operations/account_operation.ex  | 15 +++++++++++++-
 .../schemas/account_follows_request.ex        | 18 +++++++++++++++++
 .../controllers/account_controller.ex         |  5 +++--
 .../controllers/account_controller_test.exs   | 20 +++++++++++++++----
 4 files changed, 51 insertions(+), 7 deletions(-)
 create mode 100644 lib/pleroma/web/api_spec/schemas/account_follows_request.ex

diff --git a/lib/pleroma/web/api_spec/operations/account_operation.ex b/lib/pleroma/web/api_spec/operations/account_operation.ex
index 73fbe8785..9fef7ece1 100644
--- a/lib/pleroma/web/api_spec/operations/account_operation.ex
+++ b/lib/pleroma/web/api_spec/operations/account_operation.ex
@@ -10,6 +10,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.AccountFollowsRequest
   alias Pleroma.Web.ApiSpec.Schemas.AccountMuteRequest
   alias Pleroma.Web.ApiSpec.Schemas.AccountRelationship
   alias Pleroma.Web.ApiSpec.Schemas.AccountRelationshipsResponse
@@ -307,7 +308,19 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
     }
   end
 
-  def follows_operation, do: :ok
+  def follows_operation do
+    %Operation{
+      tags: ["accounts"],
+      summary: "Follows",
+      operationId: "AccountController.follows",
+      security: [%{"oAuth" => ["follow", "write:follows"]}],
+      requestBody: Helpers.request_body("Parameters", AccountFollowsRequest, required: true),
+      responses: %{
+        200 => Operation.response("Account", "application/json", Account)
+      }
+    }
+  end
+
   def mutes_operation, do: :ok
   def blocks_operation, do: :ok
   def endorsements_operation, do: :ok
diff --git a/lib/pleroma/web/api_spec/schemas/account_follows_request.ex b/lib/pleroma/web/api_spec/schemas/account_follows_request.ex
new file mode 100644
index 000000000..4fbe615d6
--- /dev/null
+++ b/lib/pleroma/web/api_spec/schemas/account_follows_request.ex
@@ -0,0 +1,18 @@
+# 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.AccountFollowsRequest do
+  alias OpenApiSpex.Schema
+  require OpenApiSpex
+
+  OpenApiSpex.schema(%{
+    title: "AccountFollowsRequest",
+    description: "POST body for muting an account",
+    type: :object,
+    properties: %{
+      uri: %Schema{type: :string}
+    },
+    required: [:uri]
+  })
+end
diff --git a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
index c1f70f32c..4340b9c84 100644
--- a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
@@ -98,7 +98,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
            :mute,
            :unmute,
            :block,
-           :unblock
+           :unblock,
+           :follows
          ]
   )
 
@@ -401,7 +402,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
   end
 
   @doc "POST /api/v1/follows"
-  def follows(%{assigns: %{user: follower}} = conn, %{"uri" => uri}) do
+  def follows(%{assigns: %{user: follower}, body_params: %{uri: uri}} = conn, _) do
     with {_, %User{} = followed} <- {:followed, User.get_cached_by_nickname(uri)},
          {_, true} <- {:followed, follower.id != followed.id},
          {:ok, follower, followed, _} <- CommonAPI.follow(follower, followed) do
diff --git a/test/web/mastodon_api/controllers/account_controller_test.exs b/test/web/mastodon_api/controllers/account_controller_test.exs
index f71b54ade..fa2091c5e 100644
--- a/test/web/mastodon_api/controllers/account_controller_test.exs
+++ b/test/web/mastodon_api/controllers/account_controller_test.exs
@@ -667,11 +667,14 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
       assert %{"id" => _id, "following" => false} = json_response(ret_conn, 200)
       assert_schema(json_response(ret_conn, 200), "AccountRelationship", ApiSpec.spec())
 
-      conn = post(conn, "/api/v1/follows", %{"uri" => other_user.nickname})
+      conn =
+        conn
+        |> put_req_header("content-type", "application/json")
+        |> post("/api/v1/follows", %{"uri" => other_user.nickname})
 
       assert %{"id" => id} = json_response(conn, 200)
       assert id == to_string(other_user.id)
-      assert_schema(json_response(conn, 200), "AccountRelationship", ApiSpec.spec())
+      assert_schema(json_response(conn, 200), "Account", ApiSpec.spec())
     end
 
     test "cancelling follow request", %{conn: conn} do
@@ -728,7 +731,12 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
 
       # self follow via uri
       user = User.get_cached_by_id(user.id)
-      conn_res = post(conn, "/api/v1/follows", %{"uri" => user.nickname})
+
+      conn_res =
+        conn
+        |> put_req_header("content-type", "multipart/form-data")
+        |> post("/api/v1/follows", %{"uri" => user.nickname})
+
       assert %{"error" => "Record not found"} = json_response(conn_res, 404)
 
       # follow non existing user
@@ -736,7 +744,11 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
       assert %{"error" => "Record not found"} = json_response(conn_res, 404)
 
       # follow non existing user via uri
-      conn_res = post(conn, "/api/v1/follows", %{"uri" => "doesntexist"})
+      conn_res =
+        conn
+        |> put_req_header("content-type", "multipart/form-data")
+        |> post("/api/v1/follows", %{"uri" => "doesntexist"})
+
       assert %{"error" => "Record not found"} = json_response(conn_res, 404)
 
       # unfollow non existing user

From 7e0b42d99f3eb9520bc29cc29c06512c55183482 Mon Sep 17 00:00:00 2001
From: Egor Kislitsyn <egor@kislitsyn.com>
Date: Thu, 9 Apr 2020 20:34:21 +0400
Subject: [PATCH 16/40] Add specs for AccountController.mutes,
 AccountController.blocks, AccountController.mutes,
 AccountController.endorsements

---
 .../api_spec/operations/account_operation.ex  | 41 +++++++++++++++++--
 .../controllers/account_controller.ex         | 23 +----------
 .../controllers/account_controller_test.exs   |  2 +
 3 files changed, 41 insertions(+), 25 deletions(-)

diff --git a/lib/pleroma/web/api_spec/operations/account_operation.ex b/lib/pleroma/web/api_spec/operations/account_operation.ex
index 9fef7ece1..9749c3b60 100644
--- a/lib/pleroma/web/api_spec/operations/account_operation.ex
+++ b/lib/pleroma/web/api_spec/operations/account_operation.ex
@@ -321,7 +321,42 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
     }
   end
 
-  def mutes_operation, do: :ok
-  def blocks_operation, do: :ok
-  def endorsements_operation, do: :ok
+  def mutes_operation do
+    %Operation{
+      tags: ["accounts"],
+      summary: "Muted accounts",
+      operationId: "AccountController.mutes",
+      description: "Accounts the user has muted.",
+      security: [%{"oAuth" => ["follow", "read:mutes"]}],
+      responses: %{
+        200 => Operation.response("Accounts", "application/json", AccountsResponse)
+      }
+    }
+  end
+
+  def blocks_operation do
+    %Operation{
+      tags: ["accounts"],
+      summary: "Blocked users",
+      operationId: "AccountController.blocks",
+      description: "View your blocks. See also accounts/:id/{block,unblock}",
+      security: [%{"oAuth" => ["read:blocks"]}],
+      responses: %{
+        200 => Operation.response("Accounts", "application/json", AccountsResponse)
+      }
+    }
+  end
+
+  def endorsements_operation do
+    %Operation{
+      tags: ["accounts"],
+      summary: "Endorsements",
+      operationId: "AccountController.endorsements",
+      description: "Not implemented",
+      security: [%{"oAuth" => ["read:accounts"]}],
+      responses: %{
+        200 => Operation.response("Empry array", "application/json", %Schema{type: :array})
+      }
+    }
+  end
 end
diff --git a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
index 4340b9c84..f72c91c51 100644
--- a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
@@ -80,28 +80,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
   plug(RateLimiter, [name: :app_account_creation] when action == :create)
   plug(:assign_account_by_id when action in @needs_account)
 
-  plug(
-    OpenApiSpex.Plug.CastAndValidate,
-    [render_error: Pleroma.Web.ApiSpec.RenderError]
-    when action in [
-           :create,
-           :verify_credentials,
-           :update_credentials,
-           :relationships,
-           :show,
-           :statuses,
-           :followers,
-           :following,
-           :lists,
-           :follow,
-           :unfollow,
-           :mute,
-           :unmute,
-           :block,
-           :unblock,
-           :follows
-         ]
-  )
+  plug(OpenApiSpex.Plug.CastAndValidate, render_error: Pleroma.Web.ApiSpec.RenderError)
 
   action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
 
diff --git a/test/web/mastodon_api/controllers/account_controller_test.exs b/test/web/mastodon_api/controllers/account_controller_test.exs
index fa2091c5e..86136f7e4 100644
--- a/test/web/mastodon_api/controllers/account_controller_test.exs
+++ b/test/web/mastodon_api/controllers/account_controller_test.exs
@@ -1155,6 +1155,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
 
     other_user_id = to_string(other_user.id)
     assert [%{"id" => ^other_user_id}] = json_response(conn, 200)
+    assert_schema(json_response(conn, 200), "AccountsResponse", ApiSpec.spec())
   end
 
   test "getting a list of blocks" do
@@ -1170,5 +1171,6 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
 
     other_user_id = to_string(other_user.id)
     assert [%{"id" => ^other_user_id}] = json_response(conn, 200)
+    assert_schema(json_response(conn, 200), "AccountsResponse", ApiSpec.spec())
   end
 end

From d3e876aeeebfcdd2821ef8310bd60b785e6df560 Mon Sep 17 00:00:00 2001
From: minibikini <egor@kislitsyn.com>
Date: Wed, 15 Apr 2020 10:26:44 +0000
Subject: [PATCH 17/40] Apply suggestion to
 lib/pleroma/web/api_spec/operations/account_operation.ex

---
 lib/pleroma/web/api_spec/operations/account_operation.ex | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/lib/pleroma/web/api_spec/operations/account_operation.ex b/lib/pleroma/web/api_spec/operations/account_operation.ex
index 9749c3b60..7ead44197 100644
--- a/lib/pleroma/web/api_spec/operations/account_operation.ex
+++ b/lib/pleroma/web/api_spec/operations/account_operation.ex
@@ -120,7 +120,8 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
         Operation.parameter(:tagged, :query, :string, "With tag"),
         Operation.parameter(:only_media, :query, BooleanLike, "Only meadia"),
         Operation.parameter(:with_muted, :query, BooleanLike, "With muted"),
-        Operation.parameter(:exclude_reblogs, :query, BooleanLike, "Exclude reblobs"),
+        Operation.parameter(:exclude_reblogs, :query, BooleanLike, "Exclude reblogs"),
+
         Operation.parameter(
           :exclude_visibilities,
           :query,

From a7feca1604fe7f22d10c0fd3284f14eae8609852 Mon Sep 17 00:00:00 2001
From: minibikini <egor@kislitsyn.com>
Date: Wed, 15 Apr 2020 10:26:53 +0000
Subject: [PATCH 18/40] Apply suggestion to
 lib/pleroma/web/api_spec/operations/account_operation.ex

---
 lib/pleroma/web/api_spec/operations/account_operation.ex | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lib/pleroma/web/api_spec/operations/account_operation.ex b/lib/pleroma/web/api_spec/operations/account_operation.ex
index 7ead44197..1c726a612 100644
--- a/lib/pleroma/web/api_spec/operations/account_operation.ex
+++ b/lib/pleroma/web/api_spec/operations/account_operation.ex
@@ -119,7 +119,7 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
         Operation.parameter(:pinned, :query, BooleanLike, "Pinned"),
         Operation.parameter(:tagged, :query, :string, "With tag"),
         Operation.parameter(:only_media, :query, BooleanLike, "Only meadia"),
-        Operation.parameter(:with_muted, :query, BooleanLike, "With muted"),
+        Operation.parameter(:with_muted, :query, BooleanLike, "Include statuses from muted acccounts."),
         Operation.parameter(:exclude_reblogs, :query, BooleanLike, "Exclude reblogs"),
 
         Operation.parameter(

From a794ba655f5a0a8b5512ad718601e5a03b9aebef Mon Sep 17 00:00:00 2001
From: minibikini <egor@kislitsyn.com>
Date: Wed, 15 Apr 2020 10:27:01 +0000
Subject: [PATCH 19/40] Apply suggestion to
 lib/pleroma/web/api_spec/operations/account_operation.ex

---
 lib/pleroma/web/api_spec/operations/account_operation.ex | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lib/pleroma/web/api_spec/operations/account_operation.ex b/lib/pleroma/web/api_spec/operations/account_operation.ex
index 1c726a612..6ce2cfe25 100644
--- a/lib/pleroma/web/api_spec/operations/account_operation.ex
+++ b/lib/pleroma/web/api_spec/operations/account_operation.ex
@@ -118,7 +118,7 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
         %Reference{"$ref": "#/components/parameters/accountIdOrNickname"},
         Operation.parameter(:pinned, :query, BooleanLike, "Pinned"),
         Operation.parameter(:tagged, :query, :string, "With tag"),
-        Operation.parameter(:only_media, :query, BooleanLike, "Only meadia"),
+        Operation.parameter(:only_media, :query, BooleanLike, "Include only statuses with media attached"),
         Operation.parameter(:with_muted, :query, BooleanLike, "Include statuses from muted acccounts."),
         Operation.parameter(:exclude_reblogs, :query, BooleanLike, "Exclude reblogs"),
 

From bfa26b09370ee049f8d70c4112709f2666c590d1 Mon Sep 17 00:00:00 2001
From: minibikini <egor@kislitsyn.com>
Date: Wed, 15 Apr 2020 10:30:19 +0000
Subject: [PATCH 20/40] Apply suggestion to
 lib/pleroma/web/api_spec/operations/account_operation.ex

---
 lib/pleroma/web/api_spec/operations/account_operation.ex | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lib/pleroma/web/api_spec/operations/account_operation.ex b/lib/pleroma/web/api_spec/operations/account_operation.ex
index 6ce2cfe25..7d4f7586d 100644
--- a/lib/pleroma/web/api_spec/operations/account_operation.ex
+++ b/lib/pleroma/web/api_spec/operations/account_operation.ex
@@ -129,7 +129,7 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
           "Exclude visibilities"
         ),
         Operation.parameter(:max_id, :query, :string, "Max ID"),
-        Operation.parameter(:min_id, :query, :string, "Mix ID"),
+        Operation.parameter(:min_id, :query, :string, "Return the oldest statuses newer than this id. "),
         Operation.parameter(:since_id, :query, :string, "Since ID"),
         Operation.parameter(
           :limit,

From a45bd91d4e79ed354ab3903b195cf74e4327d4d0 Mon Sep 17 00:00:00 2001
From: minibikini <egor@kislitsyn.com>
Date: Wed, 15 Apr 2020 10:48:32 +0000
Subject: [PATCH 21/40] Apply suggestion to
 lib/pleroma/web/api_spec/operations/account_operation.ex

---
 lib/pleroma/web/api_spec/operations/account_operation.ex | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lib/pleroma/web/api_spec/operations/account_operation.ex b/lib/pleroma/web/api_spec/operations/account_operation.ex
index 7d4f7586d..31dfbb098 100644
--- a/lib/pleroma/web/api_spec/operations/account_operation.ex
+++ b/lib/pleroma/web/api_spec/operations/account_operation.ex
@@ -116,7 +116,7 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
         "Statuses posted to the given account. Public (for public statuses only), or user token + `read:statuses` (for private statuses the user is authorized to see)",
       parameters: [
         %Reference{"$ref": "#/components/parameters/accountIdOrNickname"},
-        Operation.parameter(:pinned, :query, BooleanLike, "Pinned"),
+        Operation.parameter(:pinned, :query, BooleanLike, "Include only pinned statuses"),
         Operation.parameter(:tagged, :query, :string, "With tag"),
         Operation.parameter(:only_media, :query, BooleanLike, "Include only statuses with media attached"),
         Operation.parameter(:with_muted, :query, BooleanLike, "Include statuses from muted acccounts."),

From 81a4c15816bf4fbe3e70ba1d34adff5dfaee1cbc Mon Sep 17 00:00:00 2001
From: minibikini <egor@kislitsyn.com>
Date: Wed, 15 Apr 2020 10:48:52 +0000
Subject: [PATCH 22/40] Apply suggestion to
 lib/pleroma/web/api_spec/operations/account_operation.ex

---
 lib/pleroma/web/api_spec/operations/account_operation.ex | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lib/pleroma/web/api_spec/operations/account_operation.ex b/lib/pleroma/web/api_spec/operations/account_operation.ex
index 31dfbb098..dee28d1aa 100644
--- a/lib/pleroma/web/api_spec/operations/account_operation.ex
+++ b/lib/pleroma/web/api_spec/operations/account_operation.ex
@@ -128,7 +128,7 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
           %Schema{type: :array, items: VisibilityScope},
           "Exclude visibilities"
         ),
-        Operation.parameter(:max_id, :query, :string, "Max ID"),
+        Operation.parameter(:max_id, :query, :string, "Return statuses older than this id"),
         Operation.parameter(:min_id, :query, :string, "Return the oldest statuses newer than this id. "),
         Operation.parameter(:since_id, :query, :string, "Since ID"),
         Operation.parameter(

From 5a2e45a2189514662f46a293f764682daba7b52d Mon Sep 17 00:00:00 2001
From: minibikini <egor@kislitsyn.com>
Date: Wed, 15 Apr 2020 11:29:10 +0000
Subject: [PATCH 23/40] Apply suggestion to
 lib/pleroma/web/api_spec/operations/account_operation.ex

---
 lib/pleroma/web/api_spec/operations/account_operation.ex | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/lib/pleroma/web/api_spec/operations/account_operation.ex b/lib/pleroma/web/api_spec/operations/account_operation.ex
index dee28d1aa..92622e2ff 100644
--- a/lib/pleroma/web/api_spec/operations/account_operation.ex
+++ b/lib/pleroma/web/api_spec/operations/account_operation.ex
@@ -130,7 +130,7 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
         ),
         Operation.parameter(:max_id, :query, :string, "Return statuses older than this id"),
         Operation.parameter(:min_id, :query, :string, "Return the oldest statuses newer than this id. "),
-        Operation.parameter(:since_id, :query, :string, "Since ID"),
+        Operation.parameter(:since_id, :query, :string, "Return the newest statuses newer than this id. "),
         Operation.parameter(
           :limit,
           :query,

From 8ed162b65538ee3cb5b125587fd65657b36ca143 Mon Sep 17 00:00:00 2001
From: Egor Kislitsyn <egor@kislitsyn.com>
Date: Wed, 15 Apr 2020 15:39:32 +0400
Subject: [PATCH 24/40] Fix formatting

---
 .../api_spec/operations/account_operation.ex  | 31 +++++++++++++++----
 1 file changed, 25 insertions(+), 6 deletions(-)

diff --git a/lib/pleroma/web/api_spec/operations/account_operation.ex b/lib/pleroma/web/api_spec/operations/account_operation.ex
index 92622e2ff..6c9de51bb 100644
--- a/lib/pleroma/web/api_spec/operations/account_operation.ex
+++ b/lib/pleroma/web/api_spec/operations/account_operation.ex
@@ -118,19 +118,38 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
         %Reference{"$ref": "#/components/parameters/accountIdOrNickname"},
         Operation.parameter(:pinned, :query, BooleanLike, "Include only pinned statuses"),
         Operation.parameter(:tagged, :query, :string, "With tag"),
-        Operation.parameter(:only_media, :query, BooleanLike, "Include only statuses with media attached"),
-        Operation.parameter(:with_muted, :query, BooleanLike, "Include statuses from muted acccounts."),
+        Operation.parameter(
+          :only_media,
+          :query,
+          BooleanLike,
+          "Include only statuses with media attached"
+        ),
+        Operation.parameter(
+          :with_muted,
+          :query,
+          BooleanLike,
+          "Include statuses from muted acccounts."
+        ),
         Operation.parameter(:exclude_reblogs, :query, BooleanLike, "Exclude reblogs"),
-
         Operation.parameter(
           :exclude_visibilities,
           :query,
           %Schema{type: :array, items: VisibilityScope},
           "Exclude visibilities"
         ),
-        Operation.parameter(:max_id, :query, :string, "Return statuses older than this id"),
-        Operation.parameter(:min_id, :query, :string, "Return the oldest statuses newer than this id. "),
-        Operation.parameter(:since_id, :query, :string, "Return the newest statuses newer than this id. "),
+        Operation.parameter(:max_id, :query, :string, "Return statuses older than this ID"),
+        Operation.parameter(
+          :min_id,
+          :query,
+          :string,
+          "Return the oldest statuses newer than this ID"
+        ),
+        Operation.parameter(
+          :since_id,
+          :query,
+          :string,
+          "Return the newest statuses newer than this ID"
+        ),
         Operation.parameter(
           :limit,
           :query,

From 0e647ff55aa3128f45cd9df79b8af06da57c009e Mon Sep 17 00:00:00 2001
From: Egor Kislitsyn <egor@kislitsyn.com>
Date: Wed, 15 Apr 2020 16:45:45 +0400
Subject: [PATCH 25/40] Abstract pagination params in OpenAPI spec

---
 lib/pleroma/web/api_spec/helpers.ex           |  22 ++++
 .../api_spec/operations/account_operation.ex  | 108 ++++++------------
 2 files changed, 57 insertions(+), 73 deletions(-)

diff --git a/lib/pleroma/web/api_spec/helpers.ex b/lib/pleroma/web/api_spec/helpers.ex
index 7348dcbee..ce40fb9e8 100644
--- a/lib/pleroma/web/api_spec/helpers.ex
+++ b/lib/pleroma/web/api_spec/helpers.ex
@@ -3,6 +3,9 @@
 # SPDX-License-Identifier: AGPL-3.0-only
 
 defmodule Pleroma.Web.ApiSpec.Helpers do
+  alias OpenApiSpex.Operation
+  alias OpenApiSpex.Schema
+
   def request_body(description, schema_ref, opts \\ []) do
     media_types = ["application/json", "multipart/form-data", "application/x-www-form-urlencoded"]
 
@@ -24,4 +27,23 @@ defmodule Pleroma.Web.ApiSpec.Helpers do
       required: opts[:required] || false
     }
   end
+
+  def pagination_params do
+    [
+      Operation.parameter(:max_id, :query, :string, "Return items older than this ID"),
+      Operation.parameter(:min_id, :query, :string, "Return the oldest items newer than this ID"),
+      Operation.parameter(
+        :since_id,
+        :query,
+        :string,
+        "Return the newest items newer than this ID"
+      ),
+      Operation.parameter(
+        :limit,
+        :query,
+        %Schema{type: :integer, default: 20, maximum: 40},
+        "Limit"
+      )
+    ]
+  end
 end
diff --git a/lib/pleroma/web/api_spec/operations/account_operation.ex b/lib/pleroma/web/api_spec/operations/account_operation.ex
index 6c9de51bb..fe44a917a 100644
--- a/lib/pleroma/web/api_spec/operations/account_operation.ex
+++ b/lib/pleroma/web/api_spec/operations/account_operation.ex
@@ -6,7 +6,6 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
   alias OpenApiSpex.Operation
   alias OpenApiSpex.Reference
   alias OpenApiSpex.Schema
-  alias Pleroma.Web.ApiSpec.Helpers
   alias Pleroma.Web.ApiSpec.Schemas.Account
   alias Pleroma.Web.ApiSpec.Schemas.AccountCreateRequest
   alias Pleroma.Web.ApiSpec.Schemas.AccountCreateResponse
@@ -21,6 +20,8 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
   alias Pleroma.Web.ApiSpec.Schemas.StatusesResponse
   alias Pleroma.Web.ApiSpec.Schemas.VisibilityScope
 
+  import Pleroma.Web.ApiSpec.Helpers
+
   @spec open_api_operation(atom) :: Operation.t()
   def open_api_operation(action) do
     operation = String.to_existing_atom("#{action}_operation")
@@ -35,7 +36,7 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
       description:
         "Creates a user and account records. Returns an account access token for the app that initiated the request. The app should save this token for later, and should wait for the user to confirm their account by clicking a link in their email inbox.",
       operationId: "AccountController.create",
-      requestBody: Helpers.request_body("Parameters", AccountCreateRequest, required: true),
+      requestBody: request_body("Parameters", AccountCreateRequest, required: true),
       responses: %{
         200 => Operation.response("Account", "application/json", AccountCreateResponse)
       }
@@ -62,8 +63,7 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
       description: "Update the user's display and preferences.",
       operationId: "AccountController.update_credentials",
       security: [%{"oAuth" => ["write:accounts"]}],
-      requestBody:
-        Helpers.request_body("Parameters", AccountUpdateCredentialsRequest, required: true),
+      requestBody: request_body("Parameters", AccountUpdateCredentialsRequest, required: true),
       responses: %{
         200 => Operation.response("Account", "application/json", Account)
       }
@@ -114,49 +114,31 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
       operationId: "AccountController.statuses",
       description:
         "Statuses posted to the given account. Public (for public statuses only), or user token + `read:statuses` (for private statuses the user is authorized to see)",
-      parameters: [
-        %Reference{"$ref": "#/components/parameters/accountIdOrNickname"},
-        Operation.parameter(:pinned, :query, BooleanLike, "Include only pinned statuses"),
-        Operation.parameter(:tagged, :query, :string, "With tag"),
-        Operation.parameter(
-          :only_media,
-          :query,
-          BooleanLike,
-          "Include only statuses with media attached"
-        ),
-        Operation.parameter(
-          :with_muted,
-          :query,
-          BooleanLike,
-          "Include statuses from muted acccounts."
-        ),
-        Operation.parameter(:exclude_reblogs, :query, BooleanLike, "Exclude reblogs"),
-        Operation.parameter(
-          :exclude_visibilities,
-          :query,
-          %Schema{type: :array, items: VisibilityScope},
-          "Exclude visibilities"
-        ),
-        Operation.parameter(:max_id, :query, :string, "Return statuses older than this ID"),
-        Operation.parameter(
-          :min_id,
-          :query,
-          :string,
-          "Return the oldest statuses newer than this ID"
-        ),
-        Operation.parameter(
-          :since_id,
-          :query,
-          :string,
-          "Return the newest statuses newer than this ID"
-        ),
-        Operation.parameter(
-          :limit,
-          :query,
-          %Schema{type: :integer, default: 20, maximum: 40},
-          "Limit"
-        )
-      ],
+      parameters:
+        [
+          %Reference{"$ref": "#/components/parameters/accountIdOrNickname"},
+          Operation.parameter(:pinned, :query, BooleanLike, "Include only pinned statuses"),
+          Operation.parameter(:tagged, :query, :string, "With tag"),
+          Operation.parameter(
+            :only_media,
+            :query,
+            BooleanLike,
+            "Include only statuses with media attached"
+          ),
+          Operation.parameter(
+            :with_muted,
+            :query,
+            BooleanLike,
+            "Include statuses from muted acccounts."
+          ),
+          Operation.parameter(:exclude_reblogs, :query, BooleanLike, "Exclude reblogs"),
+          Operation.parameter(
+            :exclude_visibilities,
+            :query,
+            %Schema{type: :array, items: VisibilityScope},
+            "Exclude visibilities"
+          )
+        ] ++ pagination_params(),
       responses: %{
         200 => Operation.response("Statuses", "application/json", StatusesResponse)
       }
@@ -171,18 +153,8 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
       security: [%{"oAuth" => ["read:accounts"]}],
       description:
         "Accounts which follow the given account, if network is not hidden by the account owner.",
-      parameters: [
-        %Reference{"$ref": "#/components/parameters/accountIdOrNickname"},
-        Operation.parameter(:max_id, :query, :string, "Max ID"),
-        Operation.parameter(:min_id, :query, :string, "Mix ID"),
-        Operation.parameter(:since_id, :query, :string, "Since ID"),
-        Operation.parameter(
-          :limit,
-          :query,
-          %Schema{type: :integer, default: 20, maximum: 40},
-          "Limit"
-        )
-      ],
+      parameters:
+        [%Reference{"$ref": "#/components/parameters/accountIdOrNickname"}] ++ pagination_params(),
       responses: %{
         200 => Operation.response("Accounts", "application/json", AccountsResponse)
       }
@@ -197,18 +169,8 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
       security: [%{"oAuth" => ["read:accounts"]}],
       description:
         "Accounts which the given account is following, if network is not hidden by the account owner.",
-      parameters: [
-        %Reference{"$ref": "#/components/parameters/accountIdOrNickname"},
-        Operation.parameter(:max_id, :query, :string, "Max ID"),
-        Operation.parameter(:min_id, :query, :string, "Mix ID"),
-        Operation.parameter(:since_id, :query, :string, "Since ID"),
-        Operation.parameter(
-          :limit,
-          :query,
-          %Schema{type: :integer, default: 20, maximum: 40},
-          "Limit"
-        )
-      ],
+      parameters:
+        [%Reference{"$ref": "#/components/parameters/accountIdOrNickname"}] ++ pagination_params(),
       responses: %{200 => Operation.response("Accounts", "application/json", AccountsResponse)}
     }
   end
@@ -267,7 +229,7 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
       summary: "Mute",
       operationId: "AccountController.mute",
       security: [%{"oAuth" => ["follow", "write:mutes"]}],
-      requestBody: Helpers.request_body("Parameters", AccountMuteRequest),
+      requestBody: request_body("Parameters", AccountMuteRequest),
       description:
         "Mute the given account. Clients should filter statuses and notifications from this account, if received (e.g. due to a boost in the Home timeline).",
       parameters: [
@@ -334,7 +296,7 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
       summary: "Follows",
       operationId: "AccountController.follows",
       security: [%{"oAuth" => ["follow", "write:follows"]}],
-      requestBody: Helpers.request_body("Parameters", AccountFollowsRequest, required: true),
+      requestBody: request_body("Parameters", AccountFollowsRequest, required: true),
       responses: %{
         200 => Operation.response("Account", "application/json", Account)
       }

From 16f4787bf7e4849192d999eb2177ca7e1a34fbc9 Mon Sep 17 00:00:00 2001
From: Egor Kislitsyn <egor@kislitsyn.com>
Date: Wed, 15 Apr 2020 16:51:37 +0400
Subject: [PATCH 26/40] Add a TODO note

---
 lib/pleroma/web/activity_pub/activity_pub.ex | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex
index 1909ce097..5926a6cad 100644
--- a/lib/pleroma/web/activity_pub/activity_pub.ex
+++ b/lib/pleroma/web/activity_pub/activity_pub.ex
@@ -1157,6 +1157,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
     )
   end
 
+  # TODO: when all endpoints migrated to OpenAPI compare `pinned` with `true` (boolean) only,
+  # the same for `restrict_media/2`, `restrict_replies/2`, 'restrict_reblogs/2' and `restrict_muted/2`
   defp restrict_pinned(query, %{"pinned" => pinned, "pinned_activity_ids" => ids})
        when pinned in [true, "true", "1"] do
     from(activity in query, where: activity.id in ^ids)

From 65f04b7806e342ed8967e5fa760e1509a776036e Mon Sep 17 00:00:00 2001
From: Egor Kislitsyn <egor@kislitsyn.com>
Date: Wed, 15 Apr 2020 17:16:32 +0400
Subject: [PATCH 27/40] Fix credo warning

---
 lib/pleroma/web/activity_pub/activity_pub.ex | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex
index 5926a6cad..fa913a2aa 100644
--- a/lib/pleroma/web/activity_pub/activity_pub.ex
+++ b/lib/pleroma/web/activity_pub/activity_pub.ex
@@ -1158,7 +1158,9 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
   end
 
   # TODO: when all endpoints migrated to OpenAPI compare `pinned` with `true` (boolean) only,
-  # the same for `restrict_media/2`, `restrict_replies/2`, 'restrict_reblogs/2' and `restrict_muted/2`
+  # the same for `restrict_media/2`, `restrict_replies/2`, 'restrict_reblogs/2'
+  # and `restrict_muted/2`
+
   defp restrict_pinned(query, %{"pinned" => pinned, "pinned_activity_ids" => ids})
        when pinned in [true, "true", "1"] do
     from(activity in query, where: activity.id in ^ids)

From 163341857a726e8d74b6ddcd1230579e4c36a1b5 Mon Sep 17 00:00:00 2001
From: Egor Kislitsyn <egor@kislitsyn.com>
Date: Fri, 17 Apr 2020 19:27:22 +0400
Subject: [PATCH 28/40] Improve OpenAPI errors

---
 lib/pleroma/web/api_spec/render_error.ex      | 220 +++++++++++++++++-
 .../controllers/account_controller_test.exs   |  11 +-
 2 files changed, 222 insertions(+), 9 deletions(-)

diff --git a/lib/pleroma/web/api_spec/render_error.ex b/lib/pleroma/web/api_spec/render_error.ex
index 9184c43b6..b5877ca9c 100644
--- a/lib/pleroma/web/api_spec/render_error.ex
+++ b/lib/pleroma/web/api_spec/render_error.ex
@@ -5,23 +5,227 @@
 defmodule Pleroma.Web.ApiSpec.RenderError do
   @behaviour Plug
 
-  alias OpenApiSpex.Plug.JsonRenderError
-  alias Plug.Conn
+  import Plug.Conn, only: [put_status: 2]
+  import Phoenix.Controller, only: [json: 2]
+  import Pleroma.Web.Gettext
 
   @impl Plug
   def init(opts), do: opts
 
   @impl Plug
 
-  def call(%{private: %{open_api_spex: %{operation_id: "AccountController.create"}}} = conn, _) do
+  def call(conn, errors) do
+    errors =
+      Enum.map(errors, fn
+        %{name: nil} = err ->
+          %OpenApiSpex.Cast.Error{err | name: List.last(err.path)}
+
+        err ->
+          err
+      end)
+
     conn
-    |> Conn.put_status(:bad_request)
-    |> Phoenix.Controller.json(%{"error" => "Missing parameters"})
+    |> put_status(:bad_request)
+    |> json(%{
+      error: errors |> Enum.map(&message/1) |> Enum.join(" "),
+      errors: errors |> Enum.map(&render_error/1)
+    })
   end
 
-  def call(conn, reason) do
-    opts = JsonRenderError.init(reason)
+  defp render_error(error) do
+    pointer = OpenApiSpex.path_to_string(error)
 
-    JsonRenderError.call(conn, opts)
+    %{
+      title: "Invalid value",
+      source: %{
+        pointer: pointer
+      },
+      message: OpenApiSpex.Cast.Error.message(error)
+    }
+  end
+
+  defp message(%{reason: :invalid_schema_type, type: type, name: name}) do
+    gettext("%{name} - Invalid schema.type. Got: %{type}.",
+      name: name,
+      type: inspect(type)
+    )
+  end
+
+  defp message(%{reason: :null_value, name: name} = error) do
+    case error.type do
+      nil ->
+        gettext("%{name} - null value.", name: name)
+
+      type ->
+        gettext("%{name} - null value where %{type} expected.",
+          name: name,
+          type: type
+        )
+    end
+  end
+
+  defp message(%{reason: :all_of, meta: %{invalid_schema: invalid_schema}}) do
+    gettext(
+      "Failed to cast value as %{invalid_schema}. Value must be castable using `allOf` schemas listed.",
+      invalid_schema: invalid_schema
+    )
+  end
+
+  defp message(%{reason: :any_of, meta: %{failed_schemas: failed_schemas}}) do
+    gettext("Failed to cast value using any of: %{failed_schemas}.",
+      failed_schemas: failed_schemas
+    )
+  end
+
+  defp message(%{reason: :one_of, meta: %{failed_schemas: failed_schemas}}) do
+    gettext("Failed to cast value to one of: %{failed_schemas}.", failed_schemas: failed_schemas)
+  end
+
+  defp message(%{reason: :min_length, length: length, name: name}) do
+    gettext("%{name} - String length is smaller than minLength: %{length}.",
+      name: name,
+      length: length
+    )
+  end
+
+  defp message(%{reason: :max_length, length: length, name: name}) do
+    gettext("%{name} - String length is larger than maxLength: %{length}.",
+      name: name,
+      length: length
+    )
+  end
+
+  defp message(%{reason: :unique_items, name: name}) do
+    gettext("%{name} - Array items must be unique.", name: name)
+  end
+
+  defp message(%{reason: :min_items, length: min, value: array, name: name}) do
+    gettext("%{name} - Array length %{length} is smaller than minItems: %{min}.",
+      name: name,
+      length: length(array),
+      min: min
+    )
+  end
+
+  defp message(%{reason: :max_items, length: max, value: array, name: name}) do
+    gettext("%{name} - Array length %{length} is larger than maxItems: %{}.",
+      name: name,
+      length: length(array),
+      max: max
+    )
+  end
+
+  defp message(%{reason: :multiple_of, length: multiple, value: count, name: name}) do
+    gettext("%{name} - %{count} is not a multiple of %{multiple}.",
+      name: name,
+      count: count,
+      multiple: multiple
+    )
+  end
+
+  defp message(%{reason: :exclusive_max, length: max, value: value, name: name})
+       when value >= max do
+    gettext("%{name} - %{value} is larger than exclusive maximum %{max}.",
+      name: name,
+      value: value,
+      max: max
+    )
+  end
+
+  defp message(%{reason: :maximum, length: max, value: value, name: name})
+       when value > max do
+    gettext("%{name} - %{value} is larger than inclusive maximum %{max}.",
+      name: name,
+      value: value,
+      max: max
+    )
+  end
+
+  defp message(%{reason: :exclusive_multiple, length: min, value: value, name: name})
+       when value <= min do
+    gettext("%{name} - %{value} is smaller than exclusive minimum %{min}.",
+      name: name,
+      value: value,
+      min: min
+    )
+  end
+
+  defp message(%{reason: :minimum, length: min, value: value, name: name})
+       when value < min do
+    gettext("%{name} - %{value} is smaller than inclusive minimum %{min}.",
+      name: name,
+      value: value,
+      min: min
+    )
+  end
+
+  defp message(%{reason: :invalid_type, type: type, value: value, name: name}) do
+    gettext("%{name} - Invalid %{type}. Got: %{value}.",
+      name: name,
+      value: OpenApiSpex.TermType.type(value),
+      type: type
+    )
+  end
+
+  defp message(%{reason: :invalid_format, format: format, name: name}) do
+    gettext("%{name} - Invalid format. Expected %{format}.", name: name, format: inspect(format))
+  end
+
+  defp message(%{reason: :invalid_enum, name: name}) do
+    gettext("%{name} - Invalid value for enum.", name: name)
+  end
+
+  defp message(%{reason: :polymorphic_failed, type: polymorphic_type}) do
+    gettext("Failed to cast to any schema in %{polymorphic_type}",
+      polymorphic_type: polymorphic_type
+    )
+  end
+
+  defp message(%{reason: :unexpected_field, name: name}) do
+    gettext("Unexpected field: %{name}.", name: safe_string(name))
+  end
+
+  defp message(%{reason: :no_value_for_discriminator, name: field}) do
+    gettext("Value used as discriminator for `%{field}` matches no schemas.", name: field)
+  end
+
+  defp message(%{reason: :invalid_discriminator_value, name: field}) do
+    gettext("No value provided for required discriminator `%{field}`.", name: field)
+  end
+
+  defp message(%{reason: :unknown_schema, name: name}) do
+    gettext("Unknown schema: %{name}.", name: name)
+  end
+
+  defp message(%{reason: :missing_field, name: name}) do
+    gettext("Missing field: %{name}.", name: name)
+  end
+
+  defp message(%{reason: :missing_header, name: name}) do
+    gettext("Missing header: %{name}.", name: name)
+  end
+
+  defp message(%{reason: :invalid_header, name: name}) do
+    gettext("Invalid value for header: %{name}.", name: name)
+  end
+
+  defp message(%{reason: :max_properties, meta: meta}) do
+    gettext(
+      "Object property count %{property_count} is greater than maxProperties: %{max_properties}.",
+      property_count: meta.property_count,
+      max_properties: meta.max_properties
+    )
+  end
+
+  defp message(%{reason: :min_properties, meta: meta}) do
+    gettext(
+      "Object property count %{property_count} is less than minProperties: %{min_properties}",
+      property_count: meta.property_count,
+      min_properties: meta.min_properties
+    )
+  end
+
+  defp safe_string(string) do
+    to_string(string) |> String.slice(0..39)
   end
 end
diff --git a/test/web/mastodon_api/controllers/account_controller_test.exs b/test/web/mastodon_api/controllers/account_controller_test.exs
index 86136f7e4..133d7f642 100644
--- a/test/web/mastodon_api/controllers/account_controller_test.exs
+++ b/test/web/mastodon_api/controllers/account_controller_test.exs
@@ -952,7 +952,16 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
           |> post("/api/v1/accounts", Map.delete(valid_params, attr))
           |> json_response(400)
 
-        assert res == %{"error" => "Missing parameters"}
+        assert res == %{
+                 "error" => "Missing field: #{attr}.",
+                 "errors" => [
+                   %{
+                     "message" => "Missing field: #{attr}",
+                     "source" => %{"pointer" => "/#{attr}"},
+                     "title" => "Invalid value"
+                   }
+                 ]
+               }
       end)
     end
 

From ed3974af248a1b201d2008f1a128ee53550ef78b Mon Sep 17 00:00:00 2001
From: Egor Kislitsyn <egor@kislitsyn.com>
Date: Mon, 20 Apr 2020 18:39:05 +0400
Subject: [PATCH 29/40] Add OpenAPI spec for
 `AccountController.identity_proofs` operation

---
 .../web/api_spec/operations/account_operation.ex     | 12 ++++++++++++
 1 file changed, 12 insertions(+)

diff --git a/lib/pleroma/web/api_spec/operations/account_operation.ex b/lib/pleroma/web/api_spec/operations/account_operation.ex
index fe44a917a..d3cebaf05 100644
--- a/lib/pleroma/web/api_spec/operations/account_operation.ex
+++ b/lib/pleroma/web/api_spec/operations/account_operation.ex
@@ -341,4 +341,16 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
       }
     }
   end
+
+  def identity_proofs_operation do
+    %Operation{
+      tags: ["accounts"],
+      summary: "Identity proofs",
+      operationId: "AccountController.identity_proofs",
+      description: "Not implemented",
+      responses: %{
+        200 => Operation.response("Empry array", "application/json", %Schema{type: :array})
+      }
+    }
+  end
 end

From f0238d010a61ab935b61beebd5674593a75f17dc Mon Sep 17 00:00:00 2001
From: Egor Kislitsyn <egor@kislitsyn.com>
Date: Tue, 21 Apr 2020 23:30:24 +0400
Subject: [PATCH 30/40] Improve OpenAPI schema

- Removes unneeded wrapping in examples
- Adds `:format` attributes
---
 lib/pleroma/web/api_spec/schemas/account.ex   | 152 +++++++------
 .../schemas/account_create_request.ex         |   6 +-
 .../schemas/account_create_response.ex        |  12 +-
 .../web/api_spec/schemas/account_emoji.ex     |  18 +-
 .../web/api_spec/schemas/account_field.ex     |  14 +-
 .../schemas/account_field_attribute.ex        |   6 +-
 .../schemas/account_follows_request.ex        |   2 +-
 .../api_spec/schemas/account_relationship.ex  |  26 +--
 .../api_spec/schemas/app_create_request.ex    |   6 +-
 .../api_spec/schemas/app_create_response.ex   |   4 +-
 lib/pleroma/web/api_spec/schemas/list.ex      |   6 +-
 lib/pleroma/web/api_spec/schemas/status.ex    | 206 +++++++++---------
 12 files changed, 225 insertions(+), 233 deletions(-)

diff --git a/lib/pleroma/web/api_spec/schemas/account.ex b/lib/pleroma/web/api_spec/schemas/account.ex
index beb093182..3634a7c76 100644
--- a/lib/pleroma/web/api_spec/schemas/account.ex
+++ b/lib/pleroma/web/api_spec/schemas/account.ex
@@ -17,8 +17,8 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Account do
     type: :object,
     properties: %{
       acct: %Schema{type: :string},
-      avatar_static: %Schema{type: :string},
-      avatar: %Schema{type: :string},
+      avatar_static: %Schema{type: :string, format: :uri},
+      avatar: %Schema{type: :string, format: :uri},
       bot: %Schema{type: :boolean},
       created_at: %Schema{type: :string, format: "date-time"},
       display_name: %Schema{type: :string},
@@ -27,13 +27,13 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Account do
       follow_requests_count: %Schema{type: :integer},
       followers_count: %Schema{type: :integer},
       following_count: %Schema{type: :integer},
-      header_static: %Schema{type: :string},
-      header: %Schema{type: :string},
+      header_static: %Schema{type: :string, format: :uri},
+      header: %Schema{type: :string, format: :uri},
       id: %Schema{type: :string},
       locked: %Schema{type: :boolean},
-      note: %Schema{type: :string},
+      note: %Schema{type: :string, format: :html},
       statuses_count: %Schema{type: :integer},
-      url: %Schema{type: :string},
+      url: %Schema{type: :string, format: :uri},
       username: %Schema{type: :string},
       pleroma: %Schema{
         type: :object,
@@ -104,80 +104,78 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Account do
       }
     },
     example: %{
-      "JSON" => %{
-        "acct" => "foobar",
-        "avatar" => "https://mypleroma.com/images/avi.png",
-        "avatar_static" => "https://mypleroma.com/images/avi.png",
-        "bot" => false,
-        "created_at" => "2020-03-24T13:05:58.000Z",
-        "display_name" => "foobar",
-        "emojis" => [],
+      "acct" => "foobar",
+      "avatar" => "https://mypleroma.com/images/avi.png",
+      "avatar_static" => "https://mypleroma.com/images/avi.png",
+      "bot" => false,
+      "created_at" => "2020-03-24T13:05:58.000Z",
+      "display_name" => "foobar",
+      "emojis" => [],
+      "fields" => [],
+      "follow_requests_count" => 0,
+      "followers_count" => 0,
+      "following_count" => 1,
+      "header" => "https://mypleroma.com/images/banner.png",
+      "header_static" => "https://mypleroma.com/images/banner.png",
+      "id" => "9tKi3esbG7OQgZ2920",
+      "locked" => false,
+      "note" => "cofe",
+      "pleroma" => %{
+        "allow_following_move" => true,
+        "background_image" => nil,
+        "confirmation_pending" => true,
+        "hide_favorites" => true,
+        "hide_followers" => false,
+        "hide_followers_count" => false,
+        "hide_follows" => false,
+        "hide_follows_count" => false,
+        "is_admin" => false,
+        "is_moderator" => false,
+        "skip_thread_containment" => false,
+        "chat_token" =>
+          "SFMyNTY.g3QAAAACZAAEZGF0YW0AAAASOXRLaTNlc2JHN09RZ1oyOTIwZAAGc2lnbmVkbgYARNplS3EB.Mb_Iaqew2bN1I1o79B_iP7encmVCpTKC4OtHZRxdjKc",
+        "unread_conversation_count" => 0,
+        "tags" => [],
+        "notification_settings" => %{
+          "followers" => true,
+          "follows" => true,
+          "non_followers" => true,
+          "non_follows" => true,
+          "privacy_option" => false
+        },
+        "relationship" => %{
+          "blocked_by" => false,
+          "blocking" => false,
+          "domain_blocking" => false,
+          "endorsed" => false,
+          "followed_by" => false,
+          "following" => false,
+          "id" => "9tKi3esbG7OQgZ2920",
+          "muting" => false,
+          "muting_notifications" => false,
+          "requested" => false,
+          "showing_reblogs" => true,
+          "subscribing" => false
+        },
+        "settings_store" => %{
+          "pleroma-fe" => %{}
+        }
+      },
+      "source" => %{
         "fields" => [],
-        "follow_requests_count" => 0,
-        "followers_count" => 0,
-        "following_count" => 1,
-        "header" => "https://mypleroma.com/images/banner.png",
-        "header_static" => "https://mypleroma.com/images/banner.png",
-        "id" => "9tKi3esbG7OQgZ2920",
-        "locked" => false,
-        "note" => "cofe",
+        "note" => "foobar",
         "pleroma" => %{
-          "allow_following_move" => true,
-          "background_image" => nil,
-          "confirmation_pending" => true,
-          "hide_favorites" => true,
-          "hide_followers" => false,
-          "hide_followers_count" => false,
-          "hide_follows" => false,
-          "hide_follows_count" => false,
-          "is_admin" => false,
-          "is_moderator" => false,
-          "skip_thread_containment" => false,
-          "chat_token" =>
-            "SFMyNTY.g3QAAAACZAAEZGF0YW0AAAASOXRLaTNlc2JHN09RZ1oyOTIwZAAGc2lnbmVkbgYARNplS3EB.Mb_Iaqew2bN1I1o79B_iP7encmVCpTKC4OtHZRxdjKc",
-          "unread_conversation_count" => 0,
-          "tags" => [],
-          "notification_settings" => %{
-            "followers" => true,
-            "follows" => true,
-            "non_followers" => true,
-            "non_follows" => true,
-            "privacy_option" => false
-          },
-          "relationship" => %{
-            "blocked_by" => false,
-            "blocking" => false,
-            "domain_blocking" => false,
-            "endorsed" => false,
-            "followed_by" => false,
-            "following" => false,
-            "id" => "9tKi3esbG7OQgZ2920",
-            "muting" => false,
-            "muting_notifications" => false,
-            "requested" => false,
-            "showing_reblogs" => true,
-            "subscribing" => false
-          },
-          "settings_store" => %{
-            "pleroma-fe" => %{}
-          }
+          "actor_type" => "Person",
+          "discoverable" => false,
+          "no_rich_text" => false,
+          "show_role" => true
         },
-        "source" => %{
-          "fields" => [],
-          "note" => "foobar",
-          "pleroma" => %{
-            "actor_type" => "Person",
-            "discoverable" => false,
-            "no_rich_text" => false,
-            "show_role" => true
-          },
-          "privacy" => "public",
-          "sensitive" => false
-        },
-        "statuses_count" => 0,
-        "url" => "https://mypleroma.com/users/foobar",
-        "username" => "foobar"
-      }
+        "privacy" => "public",
+        "sensitive" => false
+      },
+      "statuses_count" => 0,
+      "url" => "https://mypleroma.com/users/foobar",
+      "username" => "foobar"
     }
   })
 end
diff --git a/lib/pleroma/web/api_spec/schemas/account_create_request.ex b/lib/pleroma/web/api_spec/schemas/account_create_request.ex
index 398e2d613..49fa12159 100644
--- a/lib/pleroma/web/api_spec/schemas/account_create_request.ex
+++ b/lib/pleroma/web/api_spec/schemas/account_create_request.ex
@@ -23,7 +23,11 @@ defmodule Pleroma.Web.ApiSpec.Schemas.AccountCreateRequest do
           "The email address to be used for login. Required when `account_activation_required` is enabled.",
         format: :email
       },
-      password: %Schema{type: :string, description: "The password to be used for login"},
+      password: %Schema{
+        type: :string,
+        description: "The password to be used for login",
+        format: :password
+      },
       agreement: %Schema{
         type: :boolean,
         description:
diff --git a/lib/pleroma/web/api_spec/schemas/account_create_response.ex b/lib/pleroma/web/api_spec/schemas/account_create_response.ex
index f41a034c0..2237351a2 100644
--- a/lib/pleroma/web/api_spec/schemas/account_create_response.ex
+++ b/lib/pleroma/web/api_spec/schemas/account_create_response.ex
@@ -15,15 +15,13 @@ defmodule Pleroma.Web.ApiSpec.Schemas.AccountCreateResponse do
       token_type: %Schema{type: :string},
       access_token: %Schema{type: :string},
       scope: %Schema{type: :array, items: %Schema{type: :string}},
-      created_at: %Schema{type: :integer}
+      created_at: %Schema{type: :integer, format: :"date-time"}
     },
     example: %{
-      "JSON" => %{
-        "access_token" => "i9hAVVzGld86Pl5JtLtizKoXVvtTlSCJvwaugCxvZzk",
-        "created_at" => 1_585_918_714,
-        "scope" => ["read", "write", "follow", "push"],
-        "token_type" => "Bearer"
-      }
+      "access_token" => "i9hAVVzGld86Pl5JtLtizKoXVvtTlSCJvwaugCxvZzk",
+      "created_at" => 1_585_918_714,
+      "scope" => ["read", "write", "follow", "push"],
+      "token_type" => "Bearer"
     }
   })
 end
diff --git a/lib/pleroma/web/api_spec/schemas/account_emoji.ex b/lib/pleroma/web/api_spec/schemas/account_emoji.ex
index 403b13b15..6c1d4d95c 100644
--- a/lib/pleroma/web/api_spec/schemas/account_emoji.ex
+++ b/lib/pleroma/web/api_spec/schemas/account_emoji.ex
@@ -13,19 +13,17 @@ defmodule Pleroma.Web.ApiSpec.Schemas.AccountEmoji do
     type: :object,
     properties: %{
       shortcode: %Schema{type: :string},
-      url: %Schema{type: :string},
-      static_url: %Schema{type: :string},
+      url: %Schema{type: :string, format: :uri},
+      static_url: %Schema{type: :string, format: :uri},
       visible_in_picker: %Schema{type: :boolean}
     },
     example: %{
-      "JSON" => %{
-        "shortcode" => "fatyoshi",
-        "url" =>
-          "https://files.mastodon.social/custom_emojis/images/000/023/920/original/e57ecb623faa0dc9.png",
-        "static_url" =>
-          "https://files.mastodon.social/custom_emojis/images/000/023/920/static/e57ecb623faa0dc9.png",
-        "visible_in_picker" => true
-      }
+      "shortcode" => "fatyoshi",
+      "url" =>
+        "https://files.mastodon.social/custom_emojis/images/000/023/920/original/e57ecb623faa0dc9.png",
+      "static_url" =>
+        "https://files.mastodon.social/custom_emojis/images/000/023/920/static/e57ecb623faa0dc9.png",
+      "visible_in_picker" => true
     }
   })
 end
diff --git a/lib/pleroma/web/api_spec/schemas/account_field.ex b/lib/pleroma/web/api_spec/schemas/account_field.ex
index 8906d812d..fa97073a0 100644
--- a/lib/pleroma/web/api_spec/schemas/account_field.ex
+++ b/lib/pleroma/web/api_spec/schemas/account_field.ex
@@ -13,16 +13,14 @@ defmodule Pleroma.Web.ApiSpec.Schemas.AccountField do
     type: :object,
     properties: %{
       name: %Schema{type: :string},
-      value: %Schema{type: :string},
-      verified_at: %Schema{type: :string, format: "date-time", nullable: true}
+      value: %Schema{type: :string, format: :html},
+      verified_at: %Schema{type: :string, format: :"date-time", nullable: true}
     },
     example: %{
-      "JSON" => %{
-        "name" => "Website",
-        "value" =>
-          "<a href=\"https://pleroma.com\" rel=\"me nofollow noopener noreferrer\" target=\"_blank\"><span class=\"invisible\">https://</span><span class=\"\">pleroma.com</span><span class=\"invisible\"></span></a>",
-        "verified_at" => "2019-08-29T04:14:55.571+00:00"
-      }
+      "name" => "Website",
+      "value" =>
+        "<a href=\"https://pleroma.com\" rel=\"me nofollow noopener noreferrer\" target=\"_blank\"><span class=\"invisible\">https://</span><span class=\"\">pleroma.com</span><span class=\"invisible\"></span></a>",
+      "verified_at" => "2019-08-29T04:14:55.571+00:00"
     }
   })
 end
diff --git a/lib/pleroma/web/api_spec/schemas/account_field_attribute.ex b/lib/pleroma/web/api_spec/schemas/account_field_attribute.ex
index fbbdf95f5..89e483655 100644
--- a/lib/pleroma/web/api_spec/schemas/account_field_attribute.ex
+++ b/lib/pleroma/web/api_spec/schemas/account_field_attribute.ex
@@ -17,10 +17,8 @@ defmodule Pleroma.Web.ApiSpec.Schemas.AccountAttributeField do
     },
     required: [:name, :value],
     example: %{
-      "JSON" => %{
-        "name" => "Website",
-        "value" => "https://pleroma.com"
-      }
+      "name" => "Website",
+      "value" => "https://pleroma.com"
     }
   })
 end
diff --git a/lib/pleroma/web/api_spec/schemas/account_follows_request.ex b/lib/pleroma/web/api_spec/schemas/account_follows_request.ex
index 4fbe615d6..19dce0cb2 100644
--- a/lib/pleroma/web/api_spec/schemas/account_follows_request.ex
+++ b/lib/pleroma/web/api_spec/schemas/account_follows_request.ex
@@ -11,7 +11,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.AccountFollowsRequest do
     description: "POST body for muting an account",
     type: :object,
     properties: %{
-      uri: %Schema{type: :string}
+      uri: %Schema{type: :string, format: :uri}
     },
     required: [:uri]
   })
diff --git a/lib/pleroma/web/api_spec/schemas/account_relationship.ex b/lib/pleroma/web/api_spec/schemas/account_relationship.ex
index 7db3b49bb..f2bd37d39 100644
--- a/lib/pleroma/web/api_spec/schemas/account_relationship.ex
+++ b/lib/pleroma/web/api_spec/schemas/account_relationship.ex
@@ -26,20 +26,18 @@ defmodule Pleroma.Web.ApiSpec.Schemas.AccountRelationship do
       subscribing: %Schema{type: :boolean}
     },
     example: %{
-      "JSON" => %{
-        "blocked_by" => false,
-        "blocking" => false,
-        "domain_blocking" => false,
-        "endorsed" => false,
-        "followed_by" => false,
-        "following" => false,
-        "id" => "9tKi3esbG7OQgZ2920",
-        "muting" => false,
-        "muting_notifications" => false,
-        "requested" => false,
-        "showing_reblogs" => true,
-        "subscribing" => false
-      }
+      "blocked_by" => false,
+      "blocking" => false,
+      "domain_blocking" => false,
+      "endorsed" => false,
+      "followed_by" => false,
+      "following" => false,
+      "id" => "9tKi3esbG7OQgZ2920",
+      "muting" => false,
+      "muting_notifications" => false,
+      "requested" => false,
+      "showing_reblogs" => true,
+      "subscribing" => false
     }
   })
 end
diff --git a/lib/pleroma/web/api_spec/schemas/app_create_request.ex b/lib/pleroma/web/api_spec/schemas/app_create_request.ex
index 8a83abef3..7e92205cf 100644
--- a/lib/pleroma/web/api_spec/schemas/app_create_request.ex
+++ b/lib/pleroma/web/api_spec/schemas/app_create_request.ex
@@ -21,7 +21,11 @@ defmodule Pleroma.Web.ApiSpec.Schemas.AppCreateRequest do
         type: :string,
         description: "Space separated list of scopes. If none is provided, defaults to `read`."
       },
-      website: %Schema{type: :string, description: "A URL to the homepage of your app"}
+      website: %Schema{
+        type: :string,
+        description: "A URL to the homepage of your app",
+        format: :uri
+      }
     },
     required: [:client_name, :redirect_uris],
     example: %{
diff --git a/lib/pleroma/web/api_spec/schemas/app_create_response.ex b/lib/pleroma/web/api_spec/schemas/app_create_response.ex
index f290fb031..3c41d4ee5 100644
--- a/lib/pleroma/web/api_spec/schemas/app_create_response.ex
+++ b/lib/pleroma/web/api_spec/schemas/app_create_response.ex
@@ -16,9 +16,9 @@ defmodule Pleroma.Web.ApiSpec.Schemas.AppCreateResponse do
       name: %Schema{type: :string},
       client_id: %Schema{type: :string},
       client_secret: %Schema{type: :string},
-      redirect_uri: %Schema{type: :string},
+      redirect_uri: %Schema{type: :string, format: :uri},
       vapid_key: %Schema{type: :string},
-      website: %Schema{type: :string, nullable: true}
+      website: %Schema{type: :string, nullable: true, format: :uri}
     },
     example: %{
       "id" => "123",
diff --git a/lib/pleroma/web/api_spec/schemas/list.ex b/lib/pleroma/web/api_spec/schemas/list.ex
index 30fa7db93..f85fac2b8 100644
--- a/lib/pleroma/web/api_spec/schemas/list.ex
+++ b/lib/pleroma/web/api_spec/schemas/list.ex
@@ -16,10 +16,8 @@ defmodule Pleroma.Web.ApiSpec.Schemas.List do
       title: %Schema{type: :string}
     },
     example: %{
-      "JSON" => %{
-        "id" => "123",
-        "title" => "my list"
-      }
+      "id" => "123",
+      "title" => "my list"
     }
   })
 end
diff --git a/lib/pleroma/web/api_spec/schemas/status.ex b/lib/pleroma/web/api_spec/schemas/status.ex
index 486c3a0fe..a022450e6 100644
--- a/lib/pleroma/web/api_spec/schemas/status.ex
+++ b/lib/pleroma/web/api_spec/schemas/status.ex
@@ -21,7 +21,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Status do
         type: :object,
         properties: %{
           name: %Schema{type: :string},
-          website: %Schema{type: :string, nullable: true}
+          website: %Schema{type: :string, nullable: true, format: :uri}
         }
       },
       bookmarked: %Schema{type: :boolean},
@@ -29,16 +29,16 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Status do
         type: :object,
         nullable: true,
         properties: %{
-          type: %Schema{type: :string},
-          provider_name: %Schema{type: :string},
-          provider_url: %Schema{type: :string},
-          url: %Schema{type: :string},
-          image: %Schema{type: :string},
+          type: %Schema{type: :string, enum: ["link", "photo", "video", "rich"]},
+          provider_name: %Schema{type: :string, nullable: true},
+          provider_url: %Schema{type: :string, format: :uri},
+          url: %Schema{type: :string, format: :uri},
+          image: %Schema{type: :string, nullable: true, format: :uri},
           title: %Schema{type: :string},
           description: %Schema{type: :string}
         }
       },
-      content: %Schema{type: :string},
+      content: %Schema{type: :string, format: :html},
       created_at: %Schema{type: :string, format: "date-time"},
       emojis: %Schema{type: :array, items: AccountEmoji},
       favourited: %Schema{type: :boolean},
@@ -53,10 +53,10 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Status do
           type: :object,
           properties: %{
             id: %Schema{type: :string},
-            url: %Schema{type: :string},
-            remote_url: %Schema{type: :string},
-            preview_url: %Schema{type: :string},
-            text_url: %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{
@@ -74,7 +74,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Status do
             id: %Schema{type: :string},
             acct: %Schema{type: :string},
             username: %Schema{type: :string},
-            url: %Schema{type: :string}
+            url: %Schema{type: :string, format: :uri}
           }
         }
       },
@@ -120,108 +120,106 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Status do
           type: :object,
           properties: %{
             name: %Schema{type: :string},
-            url: %Schema{type: :string}
+            url: %Schema{type: :string, format: :uri}
           }
         }
       },
-      uri: %Schema{type: :string},
-      url: %Schema{type: :string},
+      uri: %Schema{type: :string, format: :uri},
+      url: %Schema{type: :string, nullable: true, format: :uri},
       visibility: VisibilityScope
     },
     example: %{
-      "JSON" => %{
-        "account" => %{
-          "acct" => "nick6",
-          "avatar" => "http://localhost:4001/images/avi.png",
-          "avatar_static" => "http://localhost:4001/images/avi.png",
-          "bot" => false,
-          "created_at" => "2020-04-07T19:48:51.000Z",
-          "display_name" => "Test テスト User 6",
-          "emojis" => [],
-          "fields" => [],
-          "followers_count" => 1,
-          "following_count" => 0,
-          "header" => "http://localhost:4001/images/banner.png",
-          "header_static" => "http://localhost:4001/images/banner.png",
-          "id" => "9toJCsKN7SmSf3aj5c",
-          "locked" => false,
-          "note" => "Tester Number 6",
-          "pleroma" => %{
-            "background_image" => nil,
-            "confirmation_pending" => false,
-            "hide_favorites" => true,
-            "hide_followers" => false,
-            "hide_followers_count" => false,
-            "hide_follows" => false,
-            "hide_follows_count" => false,
-            "is_admin" => false,
-            "is_moderator" => false,
-            "relationship" => %{
-              "blocked_by" => false,
-              "blocking" => false,
-              "domain_blocking" => false,
-              "endorsed" => false,
-              "followed_by" => false,
-              "following" => true,
-              "id" => "9toJCsKN7SmSf3aj5c",
-              "muting" => false,
-              "muting_notifications" => false,
-              "requested" => false,
-              "showing_reblogs" => true,
-              "subscribing" => false
-            },
-            "skip_thread_containment" => false,
-            "tags" => []
-          },
-          "source" => %{
-            "fields" => [],
-            "note" => "Tester Number 6",
-            "pleroma" => %{"actor_type" => "Person", "discoverable" => false},
-            "sensitive" => false
-          },
-          "statuses_count" => 1,
-          "url" => "http://localhost:4001/users/nick6",
-          "username" => "nick6"
-        },
-        "application" => %{"name" => "Web", "website" => nil},
-        "bookmarked" => false,
-        "card" => nil,
-        "content" => "foobar",
+      "account" => %{
+        "acct" => "nick6",
+        "avatar" => "http://localhost:4001/images/avi.png",
+        "avatar_static" => "http://localhost:4001/images/avi.png",
+        "bot" => false,
         "created_at" => "2020-04-07T19:48:51.000Z",
+        "display_name" => "Test テスト User 6",
         "emojis" => [],
-        "favourited" => false,
-        "favourites_count" => 0,
-        "id" => "9toJCu5YZW7O7gfvH6",
-        "in_reply_to_account_id" => nil,
-        "in_reply_to_id" => nil,
-        "language" => nil,
-        "media_attachments" => [],
-        "mentions" => [],
-        "muted" => false,
-        "pinned" => false,
+        "fields" => [],
+        "followers_count" => 1,
+        "following_count" => 0,
+        "header" => "http://localhost:4001/images/banner.png",
+        "header_static" => "http://localhost:4001/images/banner.png",
+        "id" => "9toJCsKN7SmSf3aj5c",
+        "locked" => false,
+        "note" => "Tester Number 6",
         "pleroma" => %{
-          "content" => %{"text/plain" => "foobar"},
-          "conversation_id" => 345_972,
-          "direct_conversation_id" => nil,
-          "emoji_reactions" => [],
-          "expires_at" => nil,
-          "in_reply_to_account_acct" => nil,
-          "local" => true,
-          "spoiler_text" => %{"text/plain" => ""},
-          "thread_muted" => false
+          "background_image" => nil,
+          "confirmation_pending" => false,
+          "hide_favorites" => true,
+          "hide_followers" => false,
+          "hide_followers_count" => false,
+          "hide_follows" => false,
+          "hide_follows_count" => false,
+          "is_admin" => false,
+          "is_moderator" => false,
+          "relationship" => %{
+            "blocked_by" => false,
+            "blocking" => false,
+            "domain_blocking" => false,
+            "endorsed" => false,
+            "followed_by" => false,
+            "following" => true,
+            "id" => "9toJCsKN7SmSf3aj5c",
+            "muting" => false,
+            "muting_notifications" => false,
+            "requested" => false,
+            "showing_reblogs" => true,
+            "subscribing" => false
+          },
+          "skip_thread_containment" => false,
+          "tags" => []
         },
-        "poll" => nil,
-        "reblog" => nil,
-        "reblogged" => false,
-        "reblogs_count" => 0,
-        "replies_count" => 0,
-        "sensitive" => false,
-        "spoiler_text" => "",
-        "tags" => [],
-        "uri" => "http://localhost:4001/objects/0f5dad44-0e9e-4610-b377-a2631e499190",
-        "url" => "http://localhost:4001/notice/9toJCu5YZW7O7gfvH6",
-        "visibility" => "private"
-      }
+        "source" => %{
+          "fields" => [],
+          "note" => "Tester Number 6",
+          "pleroma" => %{"actor_type" => "Person", "discoverable" => false},
+          "sensitive" => false
+        },
+        "statuses_count" => 1,
+        "url" => "http://localhost:4001/users/nick6",
+        "username" => "nick6"
+      },
+      "application" => %{"name" => "Web", "website" => nil},
+      "bookmarked" => false,
+      "card" => nil,
+      "content" => "foobar",
+      "created_at" => "2020-04-07T19:48:51.000Z",
+      "emojis" => [],
+      "favourited" => false,
+      "favourites_count" => 0,
+      "id" => "9toJCu5YZW7O7gfvH6",
+      "in_reply_to_account_id" => nil,
+      "in_reply_to_id" => nil,
+      "language" => nil,
+      "media_attachments" => [],
+      "mentions" => [],
+      "muted" => false,
+      "pinned" => false,
+      "pleroma" => %{
+        "content" => %{"text/plain" => "foobar"},
+        "conversation_id" => 345_972,
+        "direct_conversation_id" => nil,
+        "emoji_reactions" => [],
+        "expires_at" => nil,
+        "in_reply_to_account_acct" => nil,
+        "local" => true,
+        "spoiler_text" => %{"text/plain" => ""},
+        "thread_muted" => false
+      },
+      "poll" => nil,
+      "reblog" => nil,
+      "reblogged" => false,
+      "reblogs_count" => 0,
+      "replies_count" => 0,
+      "sensitive" => false,
+      "spoiler_text" => "",
+      "tags" => [],
+      "uri" => "http://localhost:4001/objects/0f5dad44-0e9e-4610-b377-a2631e499190",
+      "url" => "http://localhost:4001/notice/9toJCu5YZW7O7gfvH6",
+      "visibility" => "private"
     }
   })
 end

From 11433cd38d9761ddf3fdb94f8c39526910b975c1 Mon Sep 17 00:00:00 2001
From: Egor Kislitsyn <egor@kislitsyn.com>
Date: Tue, 21 Apr 2020 23:54:45 +0400
Subject: [PATCH 31/40] Add OpenAPI schema for FlakeID

---
 lib/pleroma/web/api_spec/schemas/account.ex   | 22 ++++---------------
 .../api_spec/schemas/account_relationship.ex  |  3 ++-
 lib/pleroma/web/api_spec/schemas/flake_id.ex  | 14 ++++++++++++
 lib/pleroma/web/api_spec/schemas/poll.ex      |  3 ++-
 lib/pleroma/web/api_spec/schemas/status.ex    |  3 ++-
 5 files changed, 24 insertions(+), 21 deletions(-)
 create mode 100644 lib/pleroma/web/api_spec/schemas/flake_id.ex

diff --git a/lib/pleroma/web/api_spec/schemas/account.ex b/lib/pleroma/web/api_spec/schemas/account.ex
index 3634a7c76..f57015254 100644
--- a/lib/pleroma/web/api_spec/schemas/account.ex
+++ b/lib/pleroma/web/api_spec/schemas/account.ex
@@ -6,7 +6,9 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Account do
   alias OpenApiSpex.Schema
   alias Pleroma.Web.ApiSpec.Schemas.AccountEmoji
   alias Pleroma.Web.ApiSpec.Schemas.AccountField
+  alias Pleroma.Web.ApiSpec.Schemas.AccountRelationship
   alias Pleroma.Web.ApiSpec.Schemas.ActorType
+  alias Pleroma.Web.ApiSpec.Schemas.FlakeID
   alias Pleroma.Web.ApiSpec.Schemas.VisibilityScope
 
   require OpenApiSpex
@@ -29,7 +31,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Account do
       following_count: %Schema{type: :integer},
       header_static: %Schema{type: :string, format: :uri},
       header: %Schema{type: :string, format: :uri},
-      id: %Schema{type: :string},
+      id: FlakeID,
       locked: %Schema{type: :boolean},
       note: %Schema{type: :string, format: :html},
       statuses_count: %Schema{type: :integer},
@@ -62,23 +64,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Account do
               privacy_option: %Schema{type: :boolean}
             }
           },
-          relationship: %Schema{
-            type: :object,
-            properties: %{
-              blocked_by: %Schema{type: :boolean},
-              blocking: %Schema{type: :boolean},
-              domain_blocking: %Schema{type: :boolean},
-              endorsed: %Schema{type: :boolean},
-              followed_by: %Schema{type: :boolean},
-              following: %Schema{type: :boolean},
-              id: %Schema{type: :string},
-              muting: %Schema{type: :boolean},
-              muting_notifications: %Schema{type: :boolean},
-              requested: %Schema{type: :boolean},
-              showing_reblogs: %Schema{type: :boolean},
-              subscribing: %Schema{type: :boolean}
-            }
-          },
+          relationship: AccountRelationship,
           settings_store: %Schema{
             type: :object
           }
diff --git a/lib/pleroma/web/api_spec/schemas/account_relationship.ex b/lib/pleroma/web/api_spec/schemas/account_relationship.ex
index f2bd37d39..8b982669e 100644
--- a/lib/pleroma/web/api_spec/schemas/account_relationship.ex
+++ b/lib/pleroma/web/api_spec/schemas/account_relationship.ex
@@ -4,6 +4,7 @@
 
 defmodule Pleroma.Web.ApiSpec.Schemas.AccountRelationship do
   alias OpenApiSpex.Schema
+  alias Pleroma.Web.ApiSpec.Schemas.FlakeID
 
   require OpenApiSpex
 
@@ -18,7 +19,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.AccountRelationship do
       endorsed: %Schema{type: :boolean},
       followed_by: %Schema{type: :boolean},
       following: %Schema{type: :boolean},
-      id: %Schema{type: :string},
+      id: FlakeID,
       muting: %Schema{type: :boolean},
       muting_notifications: %Schema{type: :boolean},
       requested: %Schema{type: :boolean},
diff --git a/lib/pleroma/web/api_spec/schemas/flake_id.ex b/lib/pleroma/web/api_spec/schemas/flake_id.ex
new file mode 100644
index 000000000..b8e03b8a1
--- /dev/null
+++ b/lib/pleroma/web/api_spec/schemas/flake_id.ex
@@ -0,0 +1,14 @@
+# 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.FlakeID do
+  require OpenApiSpex
+
+  OpenApiSpex.schema(%{
+    title: "FlakeID",
+    description:
+      "Pleroma uses 128-bit ids as opposed to Mastodon's 64 bits. However just like Mastodon's ids they are sortable strings",
+    type: :string
+  })
+end
diff --git a/lib/pleroma/web/api_spec/schemas/poll.ex b/lib/pleroma/web/api_spec/schemas/poll.ex
index 2a9975f85..5fc9e889f 100644
--- a/lib/pleroma/web/api_spec/schemas/poll.ex
+++ b/lib/pleroma/web/api_spec/schemas/poll.ex
@@ -5,6 +5,7 @@
 defmodule Pleroma.Web.ApiSpec.Schemas.Poll do
   alias OpenApiSpex.Schema
   alias Pleroma.Web.ApiSpec.Schemas.AccountEmoji
+  alias Pleroma.Web.ApiSpec.Schemas.FlakeID
 
   require OpenApiSpex
 
@@ -13,7 +14,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Poll do
     description: "Response schema for account custom fields",
     type: :object,
     properties: %{
-      id: %Schema{type: :string},
+      id: FlakeID,
       expires_at: %Schema{type: :string, format: "date-time"},
       expired: %Schema{type: :boolean},
       multiple: %Schema{type: :boolean},
diff --git a/lib/pleroma/web/api_spec/schemas/status.ex b/lib/pleroma/web/api_spec/schemas/status.ex
index a022450e6..bf5f04691 100644
--- a/lib/pleroma/web/api_spec/schemas/status.ex
+++ b/lib/pleroma/web/api_spec/schemas/status.ex
@@ -6,6 +6,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Status do
   alias OpenApiSpex.Schema
   alias Pleroma.Web.ApiSpec.Schemas.Account
   alias Pleroma.Web.ApiSpec.Schemas.AccountEmoji
+  alias Pleroma.Web.ApiSpec.Schemas.FlakeID
   alias Pleroma.Web.ApiSpec.Schemas.Poll
   alias Pleroma.Web.ApiSpec.Schemas.VisibilityScope
 
@@ -43,7 +44,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Status do
       emojis: %Schema{type: :array, items: AccountEmoji},
       favourited: %Schema{type: :boolean},
       favourites_count: %Schema{type: :integer},
-      id: %Schema{type: :string},
+      id: FlakeID,
       in_reply_to_account_id: %Schema{type: :string, nullable: true},
       in_reply_to_id: %Schema{type: :string, nullable: true},
       language: %Schema{type: :string, nullable: true},

From 1b06a27746ccbbdec77b7bc1571783a64ade4431 Mon Sep 17 00:00:00 2001
From: Egor Kislitsyn <egor@kislitsyn.com>
Date: Wed, 22 Apr 2020 20:20:19 +0400
Subject: [PATCH 32/40] Update Flake ID description

---
 docs/API/differences_in_mastoapi_responses.md | 2 +-
 lib/pleroma/web/api_spec/schemas/flake_id.ex  | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/docs/API/differences_in_mastoapi_responses.md b/docs/API/differences_in_mastoapi_responses.md
index 1059155cf..62725edb4 100644
--- a/docs/API/differences_in_mastoapi_responses.md
+++ b/docs/API/differences_in_mastoapi_responses.md
@@ -4,7 +4,7 @@ A Pleroma instance can be identified by "<Mastodon version> (compatible; Pleroma
 
 ## Flake IDs
 
-Pleroma uses 128-bit ids as opposed to Mastodon's 64 bits. However just like Mastodon's ids they are sortable strings
+Pleroma uses 128-bit ids as opposed to Mastodon's 64 bits. However just like Mastodon's ids they are lexically sortable strings
 
 ## Attachment cap
 
diff --git a/lib/pleroma/web/api_spec/schemas/flake_id.ex b/lib/pleroma/web/api_spec/schemas/flake_id.ex
index b8e03b8a1..3b5f6477a 100644
--- a/lib/pleroma/web/api_spec/schemas/flake_id.ex
+++ b/lib/pleroma/web/api_spec/schemas/flake_id.ex
@@ -8,7 +8,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.FlakeID do
   OpenApiSpex.schema(%{
     title: "FlakeID",
     description:
-      "Pleroma uses 128-bit ids as opposed to Mastodon's 64 bits. However just like Mastodon's ids they are sortable strings",
+      "Pleroma uses 128-bit ids as opposed to Mastodon's 64 bits. However just like Mastodon's ids they are lexically sortable strings",
     type: :string
   })
 end

From a626cb682cc8fd6cad91484db064ed22646960af Mon Sep 17 00:00:00 2001
From: fence <fence@desu-mail.moe>
Date: Mon, 27 Apr 2020 17:55:33 +0200
Subject: [PATCH 33/40] secure mongoose auth endpoint

---
 .../web/mongooseim/mongoose_im_controller.ex  | 37 +++++++++++++------
 1 file changed, 26 insertions(+), 11 deletions(-)

diff --git a/lib/pleroma/web/mongooseim/mongoose_im_controller.ex b/lib/pleroma/web/mongooseim/mongoose_im_controller.ex
index 04d823b36..744cf5227 100644
--- a/lib/pleroma/web/mongooseim/mongoose_im_controller.ex
+++ b/lib/pleroma/web/mongooseim/mongoose_im_controller.ex
@@ -26,21 +26,36 @@ defmodule Pleroma.Web.MongooseIM.MongooseIMController do
   end
 
   def check_password(conn, %{"user" => username, "pass" => password}) do
-    with %User{password_hash: password_hash} <-
-           Repo.get_by(User, nickname: username, local: true),
-         true <- Pbkdf2.checkpw(password, password_hash) do
-      conn
-      |> json(true)
-    else
-      false ->
-        conn
-        |> put_status(:forbidden)
-        |> json(false)
+    user = Repo.get_by(User, nickname: username, local: true)
 
-      _ ->
+    case User.account_status(user) do
+      :deactivated ->
         conn
         |> put_status(:not_found)
         |> json(false)
+
+      :confirmation_pending ->
+        conn
+        |> put_status(:not_found)
+        |> json(false)
+
+      _ ->
+        with %User{password_hash: password_hash} <-
+               user,
+             true <- Pbkdf2.checkpw(password, password_hash) do
+          conn
+          |> json(true)
+        else
+          false ->
+            conn
+            |> put_status(:forbidden)
+            |> json(false)
+
+          _ ->
+            conn
+            |> put_status(:not_found)
+            |> json(false)
+        end
     end
   end
 end

From 5c7cc109172c84b991fad7eebbdd51e75f0c5382 Mon Sep 17 00:00:00 2001
From: fence <fence@desu-mail.moe>
Date: Mon, 27 Apr 2020 18:31:00 +0200
Subject: [PATCH 34/40] add tests for deactivated users for mongoose auth

---
 lib/pleroma/web/mongooseim/mongoose_im_controller.ex | 7 ++++++-
 test/web/mongooseim/mongoose_im_controller_test.exs  | 9 +++++++++
 2 files changed, 15 insertions(+), 1 deletion(-)

diff --git a/lib/pleroma/web/mongooseim/mongoose_im_controller.ex b/lib/pleroma/web/mongooseim/mongoose_im_controller.ex
index 744cf5227..c15b4bfb8 100644
--- a/lib/pleroma/web/mongooseim/mongoose_im_controller.ex
+++ b/lib/pleroma/web/mongooseim/mongoose_im_controller.ex
@@ -27,8 +27,13 @@ defmodule Pleroma.Web.MongooseIM.MongooseIMController do
 
   def check_password(conn, %{"user" => username, "pass" => password}) do
     user = Repo.get_by(User, nickname: username, local: true)
+    
+    state = case user do
+      nil -> nil
+      _ -> User.account_status(user)
+    end
 
-    case User.account_status(user) do
+    case state do
       :deactivated ->
         conn
         |> put_status(:not_found)
diff --git a/test/web/mongooseim/mongoose_im_controller_test.exs b/test/web/mongooseim/mongoose_im_controller_test.exs
index 291ae54fc..5987111e5 100644
--- a/test/web/mongooseim/mongoose_im_controller_test.exs
+++ b/test/web/mongooseim/mongoose_im_controller_test.exs
@@ -34,6 +34,7 @@ defmodule Pleroma.Web.MongooseIMController do
 
   test "/check_password", %{conn: conn} do
     user = insert(:user, password_hash: Comeonin.Pbkdf2.hashpwsalt("cool"))
+    _deactivated_user = insert(:user, nickname: "konata", local: false, deactivated: true)
 
     res =
       conn
@@ -49,6 +50,14 @@ defmodule Pleroma.Web.MongooseIMController do
 
     assert res == false
 
+    res =
+      conn
+      |> get(mongoose_im_path(conn, :check_password), user: "konata", pass: "1337")
+      |> json_response(404)
+
+    assert res == false
+
+
     res =
       conn
       |> get(mongoose_im_path(conn, :check_password), user: "nobody", pass: "cool")

From 2efc00b3cf5413ae7f8e8411ee1372343ee2618a Mon Sep 17 00:00:00 2001
From: Egor Kislitsyn <egor@kislitsyn.com>
Date: Mon, 27 Apr 2020 20:46:52 +0400
Subject: [PATCH 35/40] Use `json_response_and_validate_schema/2` in tests to
 validate OpenAPI schema

---
 .../api_spec/operations/account_operation.ex  |  27 +-
 lib/pleroma/web/api_spec/schemas/account.ex   |   2 +-
 lib/pleroma/web/controller_helper.ex          |   5 +-
 .../controllers/account_controller.ex         |   4 +-
 test/support/conn_case.ex                     |  21 +-
 .../update_credentials_test.exs               |  60 +-
 .../controllers/account_controller_test.exs   | 569 ++++++++----------
 7 files changed, 330 insertions(+), 358 deletions(-)

diff --git a/lib/pleroma/web/api_spec/operations/account_operation.ex b/lib/pleroma/web/api_spec/operations/account_operation.ex
index fcf030037..bf8d21059 100644
--- a/lib/pleroma/web/api_spec/operations/account_operation.ex
+++ b/lib/pleroma/web/api_spec/operations/account_operation.ex
@@ -7,6 +7,7 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
   alias OpenApiSpex.Reference
   alias OpenApiSpex.Schema
   alias Pleroma.Web.ApiSpec.Schemas.Account
+  alias Pleroma.Web.ApiSpec.Schemas.ApiError
   alias Pleroma.Web.ApiSpec.Schemas.AccountCreateRequest
   alias Pleroma.Web.ApiSpec.Schemas.AccountCreateResponse
   alias Pleroma.Web.ApiSpec.Schemas.AccountFollowsRequest
@@ -38,7 +39,10 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
       operationId: "AccountController.create",
       requestBody: request_body("Parameters", AccountCreateRequest, required: true),
       responses: %{
-        200 => Operation.response("Account", "application/json", AccountCreateResponse)
+        200 => Operation.response("Account", "application/json", AccountCreateResponse),
+        400 => Operation.response("Error", "application/json", ApiError),
+        403 => Operation.response("Error", "application/json", ApiError),
+        429 => Operation.response("Error", "application/json", ApiError)
       }
     }
   end
@@ -65,7 +69,8 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
       security: [%{"oAuth" => ["write:accounts"]}],
       requestBody: request_body("Parameters", AccountUpdateCredentialsRequest, required: true),
       responses: %{
-        200 => Operation.response("Account", "application/json", Account)
+        200 => Operation.response("Account", "application/json", Account),
+        403 => Operation.response("Error", "application/json", ApiError)
       }
     }
   end
@@ -102,7 +107,8 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
       description: "View information about a profile.",
       parameters: [%Reference{"$ref": "#/components/parameters/accountIdOrNickname"}],
       responses: %{
-        200 => Operation.response("Account", "application/json", Account)
+        200 => Operation.response("Account", "application/json", Account),
+        404 => Operation.response("Error", "application/json", ApiError)
       }
     }
   end
@@ -140,7 +146,8 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
           )
         ] ++ pagination_params(),
       responses: %{
-        200 => Operation.response("Statuses", "application/json", StatusesResponse)
+        200 => Operation.response("Statuses", "application/json", StatusesResponse),
+        404 => Operation.response("Error", "application/json", ApiError)
       }
     }
   end
@@ -204,7 +211,9 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
         )
       ],
       responses: %{
-        200 => Operation.response("Relationship", "application/json", AccountRelationship)
+        200 => Operation.response("Relationship", "application/json", AccountRelationship),
+        400 => Operation.response("Error", "application/json", ApiError),
+        404 => Operation.response("Error", "application/json", ApiError)
       }
     }
   end
@@ -218,7 +227,9 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
       description: "Unfollow the given account",
       parameters: [%Reference{"$ref": "#/components/parameters/accountIdOrNickname"}],
       responses: %{
-        200 => Operation.response("Relationship", "application/json", AccountRelationship)
+        200 => Operation.response("Relationship", "application/json", AccountRelationship),
+        400 => Operation.response("Error", "application/json", ApiError),
+        404 => Operation.response("Error", "application/json", ApiError)
       }
     }
   end
@@ -298,7 +309,9 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
       security: [%{"oAuth" => ["follow", "write:follows"]}],
       requestBody: request_body("Parameters", AccountFollowsRequest, required: true),
       responses: %{
-        200 => Operation.response("Account", "application/json", AccountRelationship)
+        200 => Operation.response("Account", "application/json", AccountRelationship),
+        400 => Operation.response("Error", "application/json", ApiError),
+        404 => Operation.response("Error", "application/json", ApiError)
       }
     }
   end
diff --git a/lib/pleroma/web/api_spec/schemas/account.ex b/lib/pleroma/web/api_spec/schemas/account.ex
index f57015254..d128feb30 100644
--- a/lib/pleroma/web/api_spec/schemas/account.ex
+++ b/lib/pleroma/web/api_spec/schemas/account.ex
@@ -41,7 +41,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Account do
         type: :object,
         properties: %{
           allow_following_move: %Schema{type: :boolean},
-          background_image: %Schema{type: :boolean, nullable: true},
+          background_image: %Schema{type: :string, nullable: true},
           chat_token: %Schema{type: :string},
           confirmation_pending: %Schema{type: :boolean},
           hide_favorites: %Schema{type: :boolean},
diff --git a/lib/pleroma/web/controller_helper.ex b/lib/pleroma/web/controller_helper.ex
index 4780081b2..eb97ae975 100644
--- a/lib/pleroma/web/controller_helper.ex
+++ b/lib/pleroma/web/controller_helper.ex
@@ -82,8 +82,9 @@ defmodule Pleroma.Web.ControllerHelper do
     end
   end
 
-  def assign_account_by_id(%{params: %{"id" => id}} = conn, _) do
-    case Pleroma.User.get_cached_by_id(id) do
+  def assign_account_by_id(conn, _) do
+    # TODO: use `conn.params[:id]` only after moving to OpenAPI
+    case Pleroma.User.get_cached_by_id(conn.params[:id] || conn.params["id"]) do
       %Pleroma.User{} = account -> assign(conn, :account, account)
       nil -> Pleroma.Web.MastodonAPI.FallbackController.call(conn, {:error, :not_found}) |> halt()
     end
diff --git a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
index 93df79645..b1513001b 100644
--- a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
@@ -26,6 +26,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
   alias Pleroma.Web.OAuth.Token
   alias Pleroma.Web.TwitterAPI.TwitterAPI
 
+  plug(OpenApiSpex.Plug.CastAndValidate, render_error: Pleroma.Web.ApiSpec.RenderError)
+
   plug(:skip_plug, OAuthScopesPlug when action == :identity_proofs)
 
   plug(
@@ -83,8 +85,6 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
   plug(RateLimiter, [name: :app_account_creation] when action == :create)
   plug(:assign_account_by_id when action in @needs_account)
 
-  plug(OpenApiSpex.Plug.CastAndValidate, render_error: Pleroma.Web.ApiSpec.RenderError)
-
   action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
 
   defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.AccountOperation
diff --git a/test/support/conn_case.ex b/test/support/conn_case.ex
index 8099461cc..fa30a0c41 100644
--- a/test/support/conn_case.ex
+++ b/test/support/conn_case.ex
@@ -56,7 +56,14 @@ defmodule Pleroma.Web.ConnCase do
         [conn: conn]
       end
 
-      defp json_response_and_validate_schema(conn, status \\ nil) do
+      defp json_response_and_validate_schema(
+             %{
+               private: %{
+                 open_api_spex: %{operation_id: op_id, operation_lookup: lookup, spec: spec}
+               }
+             } = conn,
+             status
+           ) do
         content_type =
           conn
           |> Plug.Conn.get_resp_header("content-type")
@@ -64,10 +71,12 @@ defmodule Pleroma.Web.ConnCase do
           |> String.split(";")
           |> List.first()
 
-        status = status || conn.status
+        status = Plug.Conn.Status.code(status)
 
-        %{private: %{open_api_spex: %{operation_id: op_id, operation_lookup: lookup, spec: spec}}} =
-          conn
+        unless lookup[op_id].responses[status] do
+          err = "Response schema not found for #{conn.status} #{conn.method} #{conn.request_path}"
+          flunk(err)
+        end
 
         schema = lookup[op_id].responses[status].content[content_type].schema
         json = json_response(conn, status)
@@ -92,6 +101,10 @@ defmodule Pleroma.Web.ConnCase do
         end
       end
 
+      defp json_response_and_validate_schema(conn, _status) do
+        flunk("Response schema not found for #{conn.method} #{conn.request_path} #{conn.status}")
+      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)
diff --git a/test/web/mastodon_api/controllers/account_controller/update_credentials_test.exs b/test/web/mastodon_api/controllers/account_controller/update_credentials_test.exs
index a3356c12f..fdb6d4c5d 100644
--- a/test/web/mastodon_api/controllers/account_controller/update_credentials_test.exs
+++ b/test/web/mastodon_api/controllers/account_controller/update_credentials_test.exs
@@ -26,7 +26,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController.UpdateCredentialsTest do
           }
         })
 
-      assert user_data = json_response(res_conn, 200)
+      assert user_data = json_response_and_validate_schema(res_conn, 200)
       assert user_data["pleroma"]["settings_store"] == %{"pleroma_fe" => %{"theme" => "bla"}}
 
       user = Repo.get(User, user_data["id"])
@@ -42,7 +42,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController.UpdateCredentialsTest do
           }
         })
 
-      assert user_data = json_response(res_conn, 200)
+      assert user_data = json_response_and_validate_schema(res_conn, 200)
 
       assert user_data["pleroma"]["settings_store"] ==
                %{
@@ -63,7 +63,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController.UpdateCredentialsTest do
           }
         })
 
-      assert user_data = json_response(res_conn, 200)
+      assert user_data = json_response_and_validate_schema(res_conn, 200)
 
       assert user_data["pleroma"]["settings_store"] ==
                %{
@@ -80,7 +80,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController.UpdateCredentialsTest do
           "note" => "I drink #cofe with @#{user2.nickname}\n\nsuya.."
         })
 
-      assert user_data = json_response(conn, 200)
+      assert user_data = json_response_and_validate_schema(conn, 200)
 
       assert user_data["note"] ==
                ~s(I drink <a class="hashtag" data-tag="cofe" href="http://localhost:4001/tag/cofe">#cofe</a> with <span class="h-card"><a class="u-url mention" data-user="#{
@@ -91,7 +91,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController.UpdateCredentialsTest do
     test "updates the user's locking status", %{conn: conn} do
       conn = patch(conn, "/api/v1/accounts/update_credentials", %{locked: "true"})
 
-      assert user_data = json_response(conn, 200)
+      assert user_data = json_response_and_validate_schema(conn, 200)
       assert user_data["locked"] == true
     end
 
@@ -101,21 +101,21 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController.UpdateCredentialsTest do
       conn = patch(conn, "/api/v1/accounts/update_credentials", %{allow_following_move: "false"})
 
       assert refresh_record(user).allow_following_move == false
-      assert user_data = json_response(conn, 200)
+      assert user_data = json_response_and_validate_schema(conn, 200)
       assert user_data["pleroma"]["allow_following_move"] == false
     end
 
     test "updates the user's default scope", %{conn: conn} do
       conn = patch(conn, "/api/v1/accounts/update_credentials", %{default_scope: "unlisted"})
 
-      assert user_data = json_response(conn, 200)
+      assert user_data = json_response_and_validate_schema(conn, 200)
       assert user_data["source"]["privacy"] == "unlisted"
     end
 
     test "updates the user's hide_followers status", %{conn: conn} do
       conn = patch(conn, "/api/v1/accounts/update_credentials", %{hide_followers: "true"})
 
-      assert user_data = json_response(conn, 200)
+      assert user_data = json_response_and_validate_schema(conn, 200)
       assert user_data["pleroma"]["hide_followers"] == true
     end
 
@@ -123,12 +123,12 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController.UpdateCredentialsTest do
       assert %{"source" => %{"pleroma" => %{"discoverable" => true}}} =
                conn
                |> patch("/api/v1/accounts/update_credentials", %{discoverable: "true"})
-               |> json_response(:ok)
+               |> json_response_and_validate_schema(:ok)
 
       assert %{"source" => %{"pleroma" => %{"discoverable" => false}}} =
                conn
                |> patch("/api/v1/accounts/update_credentials", %{discoverable: "false"})
-               |> json_response(:ok)
+               |> json_response_and_validate_schema(:ok)
     end
 
     test "updates the user's hide_followers_count and hide_follows_count", %{conn: conn} do
@@ -138,7 +138,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController.UpdateCredentialsTest do
           hide_follows_count: "true"
         })
 
-      assert user_data = json_response(conn, 200)
+      assert user_data = json_response_and_validate_schema(conn, 200)
       assert user_data["pleroma"]["hide_followers_count"] == true
       assert user_data["pleroma"]["hide_follows_count"] == true
     end
@@ -147,7 +147,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController.UpdateCredentialsTest do
       response =
         conn
         |> patch("/api/v1/accounts/update_credentials", %{skip_thread_containment: "true"})
-        |> json_response(200)
+        |> json_response_and_validate_schema(200)
 
       assert response["pleroma"]["skip_thread_containment"] == true
       assert refresh_record(user).skip_thread_containment
@@ -156,28 +156,28 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController.UpdateCredentialsTest do
     test "updates the user's hide_follows status", %{conn: conn} do
       conn = patch(conn, "/api/v1/accounts/update_credentials", %{hide_follows: "true"})
 
-      assert user_data = json_response(conn, 200)
+      assert user_data = json_response_and_validate_schema(conn, 200)
       assert user_data["pleroma"]["hide_follows"] == true
     end
 
     test "updates the user's hide_favorites status", %{conn: conn} do
       conn = patch(conn, "/api/v1/accounts/update_credentials", %{hide_favorites: "true"})
 
-      assert user_data = json_response(conn, 200)
+      assert user_data = json_response_and_validate_schema(conn, 200)
       assert user_data["pleroma"]["hide_favorites"] == true
     end
 
     test "updates the user's show_role status", %{conn: conn} do
       conn = patch(conn, "/api/v1/accounts/update_credentials", %{show_role: "false"})
 
-      assert user_data = json_response(conn, 200)
+      assert user_data = json_response_and_validate_schema(conn, 200)
       assert user_data["source"]["pleroma"]["show_role"] == false
     end
 
     test "updates the user's no_rich_text status", %{conn: conn} do
       conn = patch(conn, "/api/v1/accounts/update_credentials", %{no_rich_text: "true"})
 
-      assert user_data = json_response(conn, 200)
+      assert user_data = json_response_and_validate_schema(conn, 200)
       assert user_data["source"]["pleroma"]["no_rich_text"] == true
     end
 
@@ -185,7 +185,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController.UpdateCredentialsTest do
       conn =
         patch(conn, "/api/v1/accounts/update_credentials", %{"display_name" => "markorepairs"})
 
-      assert user_data = json_response(conn, 200)
+      assert user_data = json_response_and_validate_schema(conn, 200)
       assert user_data["display_name"] == "markorepairs"
     end
 
@@ -198,7 +198,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController.UpdateCredentialsTest do
 
       conn = patch(conn, "/api/v1/accounts/update_credentials", %{"avatar" => new_avatar})
 
-      assert user_response = json_response(conn, 200)
+      assert user_response = json_response_and_validate_schema(conn, 200)
       assert user_response["avatar"] != User.avatar_url(user)
     end
 
@@ -211,7 +211,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController.UpdateCredentialsTest do
 
       conn = patch(conn, "/api/v1/accounts/update_credentials", %{"header" => new_header})
 
-      assert user_response = json_response(conn, 200)
+      assert user_response = json_response_and_validate_schema(conn, 200)
       assert user_response["header"] != User.banner_url(user)
     end
 
@@ -227,7 +227,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController.UpdateCredentialsTest do
           "pleroma_background_image" => new_header
         })
 
-      assert user_response = json_response(conn, 200)
+      assert user_response = json_response_and_validate_schema(conn, 200)
       assert user_response["pleroma"]["background_image"]
     end
 
@@ -244,9 +244,9 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController.UpdateCredentialsTest do
 
         if token == token1 do
           assert %{"error" => "Insufficient permissions: write:accounts."} ==
-                   json_response(conn, 403)
+                   json_response_and_validate_schema(conn, 403)
         else
-          assert json_response(conn, 200)
+          assert json_response_and_validate_schema(conn, 200)
         end
       end
     end
@@ -261,11 +261,11 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController.UpdateCredentialsTest do
           "display_name" => name
         })
 
-      assert json_response(ret_conn, 200)
+      assert json_response_and_validate_schema(ret_conn, 200)
 
       conn = get(conn, "/api/v1/accounts/#{user.id}")
 
-      assert user_data = json_response(conn, 200)
+      assert user_data = json_response_and_validate_schema(conn, 200)
 
       assert user_data["note"] == note
       assert user_data["display_name"] == name
@@ -281,7 +281,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController.UpdateCredentialsTest do
       account_data =
         conn
         |> patch("/api/v1/accounts/update_credentials", %{"fields_attributes" => fields})
-        |> json_response(200)
+        |> json_response_and_validate_schema(200)
 
       assert account_data["fields"] == [
                %{"name" => "<a href=\"http://google.com\">foo</a>", "value" => "bar"},
@@ -314,7 +314,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController.UpdateCredentialsTest do
         conn
         |> put_req_header("content-type", "application/x-www-form-urlencoded")
         |> patch("/api/v1/accounts/update_credentials", fields)
-        |> json_response(200)
+        |> json_response_and_validate_schema(200)
 
       assert account["fields"] == [
                %{"name" => "foo", "value" => "bar"},
@@ -339,7 +339,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController.UpdateCredentialsTest do
       account =
         conn
         |> patch("/api/v1/accounts/update_credentials", %{"fields_attributes" => fields})
-        |> json_response(200)
+        |> json_response_and_validate_schema(200)
 
       assert account["fields"] == [
                %{"name" => "foo", "value" => ""}
@@ -358,14 +358,14 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController.UpdateCredentialsTest do
       assert %{"error" => "Invalid request"} ==
                conn
                |> patch("/api/v1/accounts/update_credentials", %{"fields_attributes" => fields})
-               |> json_response(403)
+               |> json_response_and_validate_schema(403)
 
       fields = [%{"name" => long_name, "value" => "bar"}]
 
       assert %{"error" => "Invalid request"} ==
                conn
                |> patch("/api/v1/accounts/update_credentials", %{"fields_attributes" => fields})
-               |> json_response(403)
+               |> json_response_and_validate_schema(403)
 
       Pleroma.Config.put([:instance, :max_account_fields], 1)
 
@@ -377,7 +377,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController.UpdateCredentialsTest do
       assert %{"error" => "Invalid request"} ==
                conn
                |> patch("/api/v1/accounts/update_credentials", %{"fields_attributes" => fields})
-               |> json_response(403)
+               |> json_response_and_validate_schema(403)
     end
   end
 end
diff --git a/test/web/mastodon_api/controllers/account_controller_test.exs b/test/web/mastodon_api/controllers/account_controller_test.exs
index d885b5e08..ba70ba66c 100644
--- a/test/web/mastodon_api/controllers/account_controller_test.exs
+++ b/test/web/mastodon_api/controllers/account_controller_test.exs
@@ -10,54 +10,46 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
   alias Pleroma.User
   alias Pleroma.Web.ActivityPub.ActivityPub
   alias Pleroma.Web.ActivityPub.InternalFetchActor
-  alias Pleroma.Web.ApiSpec
   alias Pleroma.Web.CommonAPI
   alias Pleroma.Web.OAuth.Token
 
-  import OpenApiSpex.TestAssertions
   import Pleroma.Factory
 
   describe "account fetching" do
     setup do: clear_config([:instance, :limit_to_local_content])
 
     test "works by id" do
-      user = insert(:user)
+      %User{id: user_id} = insert(:user)
 
-      conn =
-        build_conn()
-        |> get("/api/v1/accounts/#{user.id}")
+      assert %{"id" => ^user_id} =
+               build_conn()
+               |> get("/api/v1/accounts/#{user_id}")
+               |> json_response_and_validate_schema(200)
 
-      assert %{"id" => id} = json_response(conn, 200)
-      assert id == to_string(user.id)
-
-      conn =
-        build_conn()
-        |> get("/api/v1/accounts/-1")
-
-      assert %{"error" => "Can't find user"} = json_response(conn, 404)
+      assert %{"error" => "Can't find user"} =
+               build_conn()
+               |> get("/api/v1/accounts/-1")
+               |> json_response_and_validate_schema(404)
     end
 
     test "works by nickname" do
       user = insert(:user)
 
-      conn =
-        build_conn()
-        |> get("/api/v1/accounts/#{user.nickname}")
-
-      assert %{"id" => id} = json_response(conn, 200)
-      assert id == user.id
+      assert %{"id" => user_id} =
+               build_conn()
+               |> get("/api/v1/accounts/#{user.nickname}")
+               |> json_response_and_validate_schema(200)
     end
 
     test "works by nickname for remote users" do
       Config.put([:instance, :limit_to_local_content], false)
+
       user = insert(:user, nickname: "user@example.com", local: false)
 
-      conn =
-        build_conn()
-        |> get("/api/v1/accounts/#{user.nickname}")
-
-      assert %{"id" => id} = json_response(conn, 200)
-      assert id == user.id
+      assert %{"id" => user_id} =
+               build_conn()
+               |> get("/api/v1/accounts/#{user.nickname}")
+               |> json_response_and_validate_schema(200)
     end
 
     test "respects limit_to_local_content == :all for remote user nicknames" do
@@ -65,11 +57,9 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
 
       user = insert(:user, nickname: "user@example.com", local: false)
 
-      conn =
-        build_conn()
-        |> get("/api/v1/accounts/#{user.nickname}")
-
-      assert json_response(conn, 404)
+      assert build_conn()
+             |> get("/api/v1/accounts/#{user.nickname}")
+             |> json_response_and_validate_schema(404)
     end
 
     test "respects limit_to_local_content == :unauthenticated for remote user nicknames" do
@@ -82,7 +72,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
         build_conn()
         |> get("/api/v1/accounts/#{user.nickname}")
 
-      assert json_response(conn, 404)
+      assert json_response_and_validate_schema(conn, 404)
 
       conn =
         build_conn()
@@ -90,7 +80,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
         |> assign(:token, insert(:oauth_token, user: reading_user, scopes: ["read:accounts"]))
         |> get("/api/v1/accounts/#{user.nickname}")
 
-      assert %{"id" => id} = json_response(conn, 200)
+      assert %{"id" => id} = json_response_and_validate_schema(conn, 200)
       assert id == user.id
     end
 
@@ -101,21 +91,21 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
       user_one = insert(:user, %{id: 1212})
       user_two = insert(:user, %{nickname: "#{user_one.id}garbage"})
 
-      resp_one =
+      acc_one =
         conn
         |> get("/api/v1/accounts/#{user_one.id}")
+        |> json_response_and_validate_schema(:ok)
 
-      resp_two =
+      acc_two =
         conn
         |> get("/api/v1/accounts/#{user_two.nickname}")
+        |> json_response_and_validate_schema(:ok)
 
-      resp_three =
+      acc_three =
         conn
         |> get("/api/v1/accounts/#{user_two.id}")
+        |> json_response_and_validate_schema(:ok)
 
-      acc_one = json_response(resp_one, 200)
-      acc_two = json_response(resp_two, 200)
-      acc_three = json_response(resp_three, 200)
       refute acc_one == acc_two
       assert acc_two == acc_three
     end
@@ -123,23 +113,19 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
     test "returns 404 when user is invisible", %{conn: conn} do
       user = insert(:user, %{invisible: true})
 
-      resp =
-        conn
-        |> get("/api/v1/accounts/#{user.nickname}")
-        |> json_response(404)
-
-      assert %{"error" => "Can't find user"} = resp
+      assert %{"error" => "Can't find user"} =
+               conn
+               |> get("/api/v1/accounts/#{user.nickname}")
+               |> json_response_and_validate_schema(404)
     end
 
     test "returns 404 for internal.fetch actor", %{conn: conn} do
       %User{nickname: "internal.fetch"} = InternalFetchActor.get_actor()
 
-      resp =
-        conn
-        |> get("/api/v1/accounts/internal.fetch")
-        |> json_response(404)
-
-      assert %{"error" => "Can't find user"} = resp
+      assert %{"error" => "Can't find user"} =
+               conn
+               |> get("/api/v1/accounts/internal.fetch")
+               |> json_response_and_validate_schema(404)
     end
   end
 
@@ -157,27 +143,25 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
     setup do: clear_config([:restrict_unauthenticated, :profiles, :remote], true)
 
     test "if user is unauthenticated", %{conn: conn, local: local, remote: remote} do
-      res_conn = get(conn, "/api/v1/accounts/#{local.id}")
+      assert %{"error" => "Can't find user"} ==
+               conn
+               |> get("/api/v1/accounts/#{local.id}")
+               |> json_response_and_validate_schema(:not_found)
 
-      assert json_response(res_conn, :not_found) == %{
-               "error" => "Can't find user"
-             }
-
-      res_conn = get(conn, "/api/v1/accounts/#{remote.id}")
-
-      assert json_response(res_conn, :not_found) == %{
-               "error" => "Can't find user"
-             }
+      assert %{"error" => "Can't find user"} ==
+               conn
+               |> get("/api/v1/accounts/#{remote.id}")
+               |> json_response_and_validate_schema(:not_found)
     end
 
     test "if user is authenticated", %{local: local, remote: remote} do
       %{conn: conn} = oauth_access(["read"])
 
       res_conn = get(conn, "/api/v1/accounts/#{local.id}")
-      assert %{"id" => _} = json_response(res_conn, 200)
+      assert %{"id" => _} = json_response_and_validate_schema(res_conn, 200)
 
       res_conn = get(conn, "/api/v1/accounts/#{remote.id}")
-      assert %{"id" => _} = json_response(res_conn, 200)
+      assert %{"id" => _} = json_response_and_validate_schema(res_conn, 200)
     end
   end
 
@@ -189,22 +173,22 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
     test "if user is unauthenticated", %{conn: conn, local: local, remote: remote} do
       res_conn = get(conn, "/api/v1/accounts/#{local.id}")
 
-      assert json_response(res_conn, :not_found) == %{
+      assert json_response_and_validate_schema(res_conn, :not_found) == %{
                "error" => "Can't find user"
              }
 
       res_conn = get(conn, "/api/v1/accounts/#{remote.id}")
-      assert %{"id" => _} = json_response(res_conn, 200)
+      assert %{"id" => _} = json_response_and_validate_schema(res_conn, 200)
     end
 
     test "if user is authenticated", %{local: local, remote: remote} do
       %{conn: conn} = oauth_access(["read"])
 
       res_conn = get(conn, "/api/v1/accounts/#{local.id}")
-      assert %{"id" => _} = json_response(res_conn, 200)
+      assert %{"id" => _} = json_response_and_validate_schema(res_conn, 200)
 
       res_conn = get(conn, "/api/v1/accounts/#{remote.id}")
-      assert %{"id" => _} = json_response(res_conn, 200)
+      assert %{"id" => _} = json_response_and_validate_schema(res_conn, 200)
     end
   end
 
@@ -215,11 +199,11 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
 
     test "if user is unauthenticated", %{conn: conn, local: local, remote: remote} do
       res_conn = get(conn, "/api/v1/accounts/#{local.id}")
-      assert %{"id" => _} = json_response(res_conn, 200)
+      assert %{"id" => _} = json_response_and_validate_schema(res_conn, 200)
 
       res_conn = get(conn, "/api/v1/accounts/#{remote.id}")
 
-      assert json_response(res_conn, :not_found) == %{
+      assert json_response_and_validate_schema(res_conn, :not_found) == %{
                "error" => "Can't find user"
              }
     end
@@ -228,10 +212,10 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
       %{conn: conn} = oauth_access(["read"])
 
       res_conn = get(conn, "/api/v1/accounts/#{local.id}")
-      assert %{"id" => _} = json_response(res_conn, 200)
+      assert %{"id" => _} = json_response_and_validate_schema(res_conn, 200)
 
       res_conn = get(conn, "/api/v1/accounts/#{remote.id}")
-      assert %{"id" => _} = json_response(res_conn, 200)
+      assert %{"id" => _} = json_response_and_validate_schema(res_conn, 200)
     end
   end
 
@@ -247,28 +231,37 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
       {:ok, activity} = CommonAPI.post(user_two, %{"status" => "User one sux0rz"})
       {:ok, repeat, _} = CommonAPI.repeat(activity.id, user_three)
 
-      assert resp = get(conn, "/api/v1/accounts/#{user_two.id}/statuses") |> json_response(200)
+      assert resp =
+               conn
+               |> get("/api/v1/accounts/#{user_two.id}/statuses")
+               |> json_response_and_validate_schema(200)
+
       assert [%{"id" => id}] = resp
-      assert_schema(resp, "StatusesResponse", ApiSpec.spec())
       assert id == activity.id
 
       # Even a blocked user will deliver the full user timeline, there would be
       #   no point in looking at a blocked users timeline otherwise
-      assert resp = get(conn, "/api/v1/accounts/#{user_two.id}/statuses") |> json_response(200)
+      assert resp =
+               conn
+               |> get("/api/v1/accounts/#{user_two.id}/statuses")
+               |> json_response_and_validate_schema(200)
+
       assert [%{"id" => id}] = resp
       assert id == activity.id
-      assert_schema(resp, "StatusesResponse", ApiSpec.spec())
 
       # Third user's timeline includes the repeat when viewed by unauthenticated user
-      resp = get(build_conn(), "/api/v1/accounts/#{user_three.id}/statuses") |> json_response(200)
+      resp =
+        build_conn()
+        |> get("/api/v1/accounts/#{user_three.id}/statuses")
+        |> json_response_and_validate_schema(200)
+
       assert [%{"id" => id}] = resp
       assert id == repeat.id
-      assert_schema(resp, "StatusesResponse", ApiSpec.spec())
 
       # When viewing a third user's timeline, the blocked users' statuses will NOT be shown
       resp = get(conn, "/api/v1/accounts/#{user_three.id}/statuses")
 
-      assert [] = json_response(resp, 200)
+      assert [] == json_response_and_validate_schema(resp, 200)
     end
 
     test "gets users statuses", %{conn: conn} do
@@ -289,34 +282,36 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
       {:ok, private_activity} =
         CommonAPI.post(user_one, %{"status" => "private", "visibility" => "private"})
 
-      resp = get(conn, "/api/v1/accounts/#{user_one.id}/statuses") |> json_response(200)
+      # TODO!!!
+      resp =
+        conn
+        |> get("/api/v1/accounts/#{user_one.id}/statuses")
+        |> json_response_and_validate_schema(200)
+
       assert [%{"id" => id}] = resp
       assert id == to_string(activity.id)
-      assert_schema(resp, "StatusesResponse", ApiSpec.spec())
 
       resp =
         conn
         |> assign(:user, user_two)
         |> assign(:token, insert(:oauth_token, user: user_two, scopes: ["read:statuses"]))
         |> get("/api/v1/accounts/#{user_one.id}/statuses")
-        |> json_response(200)
+        |> json_response_and_validate_schema(200)
 
       assert [%{"id" => id_one}, %{"id" => id_two}] = resp
       assert id_one == to_string(direct_activity.id)
       assert id_two == to_string(activity.id)
-      assert_schema(resp, "StatusesResponse", ApiSpec.spec())
 
       resp =
         conn
         |> assign(:user, user_three)
         |> assign(:token, insert(:oauth_token, user: user_three, scopes: ["read:statuses"]))
         |> get("/api/v1/accounts/#{user_one.id}/statuses")
-        |> json_response(200)
+        |> json_response_and_validate_schema(200)
 
       assert [%{"id" => id_one}, %{"id" => id_two}] = resp
       assert id_one == to_string(private_activity.id)
       assert id_two == to_string(activity.id)
-      assert_schema(resp, "StatusesResponse", ApiSpec.spec())
     end
 
     test "unimplemented pinned statuses feature", %{conn: conn} do
@@ -325,7 +320,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
 
       conn = get(conn, "/api/v1/accounts/#{user.id}/statuses?pinned=true")
 
-      assert json_response(conn, 200) == []
+      assert json_response_and_validate_schema(conn, 200) == []
     end
 
     test "gets an users media", %{conn: conn} do
@@ -340,61 +335,48 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
 
       {:ok, %{id: media_id}} = ActivityPub.upload(file, actor: user.ap_id)
 
-      {:ok, image_post} = CommonAPI.post(user, %{"status" => "cofe", "media_ids" => [media_id]})
+      {:ok, %{id: image_post_id}} =
+        CommonAPI.post(user, %{"status" => "cofe", "media_ids" => [media_id]})
 
       conn = get(conn, "/api/v1/accounts/#{user.id}/statuses?only_media=true")
 
-      assert [%{"id" => id}] = json_response(conn, 200)
-      assert id == to_string(image_post.id)
-      assert_schema(json_response(conn, 200), "StatusesResponse", ApiSpec.spec())
+      assert [%{"id" => ^image_post_id}] = json_response_and_validate_schema(conn, 200)
 
       conn = get(build_conn(), "/api/v1/accounts/#{user.id}/statuses?only_media=1")
 
-      assert [%{"id" => id}] = json_response(conn, 200)
-      assert id == to_string(image_post.id)
-      assert_schema(json_response(conn, 200), "StatusesResponse", ApiSpec.spec())
+      assert [%{"id" => ^image_post_id}] = json_response_and_validate_schema(conn, 200)
     end
 
     test "gets a user's statuses without reblogs", %{user: user, conn: conn} do
-      {:ok, post} = CommonAPI.post(user, %{"status" => "HI!!!"})
-      {:ok, _, _} = CommonAPI.repeat(post.id, user)
+      {:ok, %{id: post_id}} = CommonAPI.post(user, %{"status" => "HI!!!"})
+      {:ok, _, _} = CommonAPI.repeat(post_id, user)
 
       conn = get(conn, "/api/v1/accounts/#{user.id}/statuses?exclude_reblogs=true")
-
-      assert [%{"id" => id}] = json_response(conn, 200)
-      assert id == to_string(post.id)
-      assert_schema(json_response(conn, 200), "StatusesResponse", ApiSpec.spec())
+      assert [%{"id" => ^post_id}] = json_response_and_validate_schema(conn, 200)
 
       conn = get(conn, "/api/v1/accounts/#{user.id}/statuses?exclude_reblogs=1")
-
-      assert [%{"id" => id}] = json_response(conn, 200)
-      assert id == to_string(post.id)
-      assert_schema(json_response(conn, 200), "StatusesResponse", ApiSpec.spec())
+      assert [%{"id" => ^post_id}] = json_response_and_validate_schema(conn, 200)
     end
 
     test "filters user's statuses by a hashtag", %{user: user, conn: conn} do
-      {:ok, post} = CommonAPI.post(user, %{"status" => "#hashtag"})
+      {:ok, %{id: post_id}} = CommonAPI.post(user, %{"status" => "#hashtag"})
       {:ok, _post} = CommonAPI.post(user, %{"status" => "hashtag"})
 
       conn = get(conn, "/api/v1/accounts/#{user.id}/statuses?tagged=hashtag")
-
-      assert [%{"id" => id}] = json_response(conn, 200)
-      assert id == to_string(post.id)
-      assert_schema(json_response(conn, 200), "StatusesResponse", ApiSpec.spec())
+      assert [%{"id" => ^post_id}] = json_response_and_validate_schema(conn, 200)
     end
 
     test "the user views their own timelines and excludes direct messages", %{
       user: user,
       conn: conn
     } do
-      {:ok, public_activity} = CommonAPI.post(user, %{"status" => ".", "visibility" => "public"})
+      {:ok, %{id: public_activity_id}} =
+        CommonAPI.post(user, %{"status" => ".", "visibility" => "public"})
+
       {:ok, _direct_activity} = CommonAPI.post(user, %{"status" => ".", "visibility" => "direct"})
 
       conn = get(conn, "/api/v1/accounts/#{user.id}/statuses?exclude_visibilities[]=direct")
-
-      assert [%{"id" => id}] = json_response(conn, 200)
-      assert id == to_string(public_activity.id)
-      assert_schema(json_response(conn, 200), "StatusesResponse", ApiSpec.spec())
+      assert [%{"id" => ^public_activity_id}] = json_response_and_validate_schema(conn, 200)
     end
   end
 
@@ -414,29 +396,25 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
     setup do: clear_config([:restrict_unauthenticated, :profiles, :remote], true)
 
     test "if user is unauthenticated", %{conn: conn, local: local, remote: remote} do
-      res_conn = get(conn, "/api/v1/accounts/#{local.id}/statuses")
+      assert %{"error" => "Can't find user"} ==
+               conn
+               |> get("/api/v1/accounts/#{local.id}/statuses")
+               |> json_response_and_validate_schema(:not_found)
 
-      assert json_response(res_conn, :not_found) == %{
-               "error" => "Can't find user"
-             }
-
-      res_conn = get(conn, "/api/v1/accounts/#{remote.id}/statuses")
-
-      assert json_response(res_conn, :not_found) == %{
-               "error" => "Can't find user"
-             }
+      assert %{"error" => "Can't find user"} ==
+               conn
+               |> get("/api/v1/accounts/#{remote.id}/statuses")
+               |> json_response_and_validate_schema(:not_found)
     end
 
     test "if user is authenticated", %{local: local, remote: remote} do
       %{conn: conn} = oauth_access(["read"])
 
       res_conn = get(conn, "/api/v1/accounts/#{local.id}/statuses")
-      assert length(json_response(res_conn, 200)) == 1
-      assert_schema(json_response(res_conn, 200), "StatusesResponse", ApiSpec.spec())
+      assert length(json_response_and_validate_schema(res_conn, 200)) == 1
 
       res_conn = get(conn, "/api/v1/accounts/#{remote.id}/statuses")
-      assert length(json_response(res_conn, 200)) == 1
-      assert_schema(json_response(res_conn, 200), "StatusesResponse", ApiSpec.spec())
+      assert length(json_response_and_validate_schema(res_conn, 200)) == 1
     end
   end
 
@@ -447,27 +425,23 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
     setup do: clear_config([:restrict_unauthenticated, :profiles, :local], true)
 
     test "if user is unauthenticated", %{conn: conn, local: local, remote: remote} do
-      res_conn = get(conn, "/api/v1/accounts/#{local.id}/statuses")
-
-      assert json_response(res_conn, :not_found) == %{
-               "error" => "Can't find user"
-             }
+      assert %{"error" => "Can't find user"} ==
+               conn
+               |> get("/api/v1/accounts/#{local.id}/statuses")
+               |> json_response_and_validate_schema(:not_found)
 
       res_conn = get(conn, "/api/v1/accounts/#{remote.id}/statuses")
-      assert length(json_response(res_conn, 200)) == 1
-      assert_schema(json_response(res_conn, 200), "StatusesResponse", ApiSpec.spec())
+      assert length(json_response_and_validate_schema(res_conn, 200)) == 1
     end
 
     test "if user is authenticated", %{local: local, remote: remote} do
       %{conn: conn} = oauth_access(["read"])
 
       res_conn = get(conn, "/api/v1/accounts/#{local.id}/statuses")
-      assert length(json_response(res_conn, 200)) == 1
-      assert_schema(json_response(res_conn, 200), "StatusesResponse", ApiSpec.spec())
+      assert length(json_response_and_validate_schema(res_conn, 200)) == 1
 
       res_conn = get(conn, "/api/v1/accounts/#{remote.id}/statuses")
-      assert length(json_response(res_conn, 200)) == 1
-      assert_schema(json_response(res_conn, 200), "StatusesResponse", ApiSpec.spec())
+      assert length(json_response_and_validate_schema(res_conn, 200)) == 1
     end
   end
 
@@ -479,26 +453,22 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
 
     test "if user is unauthenticated", %{conn: conn, local: local, remote: remote} do
       res_conn = get(conn, "/api/v1/accounts/#{local.id}/statuses")
-      assert length(json_response(res_conn, 200)) == 1
-      assert_schema(json_response(res_conn, 200), "StatusesResponse", ApiSpec.spec())
+      assert length(json_response_and_validate_schema(res_conn, 200)) == 1
 
-      res_conn = get(conn, "/api/v1/accounts/#{remote.id}/statuses")
-
-      assert json_response(res_conn, :not_found) == %{
-               "error" => "Can't find user"
-             }
+      assert %{"error" => "Can't find user"} ==
+               conn
+               |> get("/api/v1/accounts/#{remote.id}/statuses")
+               |> json_response_and_validate_schema(:not_found)
     end
 
     test "if user is authenticated", %{local: local, remote: remote} do
       %{conn: conn} = oauth_access(["read"])
 
       res_conn = get(conn, "/api/v1/accounts/#{local.id}/statuses")
-      assert length(json_response(res_conn, 200)) == 1
-      assert_schema(json_response(res_conn, 200), "StatusesResponse", ApiSpec.spec())
+      assert length(json_response_and_validate_schema(res_conn, 200)) == 1
 
       res_conn = get(conn, "/api/v1/accounts/#{remote.id}/statuses")
-      assert length(json_response(res_conn, 200)) == 1
-      assert_schema(json_response(res_conn, 200), "StatusesResponse", ApiSpec.spec())
+      assert length(json_response_and_validate_schema(res_conn, 200)) == 1
     end
   end
 
@@ -507,13 +477,11 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
 
     test "getting followers", %{user: user, conn: conn} do
       other_user = insert(:user)
-      {:ok, user} = User.follow(user, other_user)
+      {:ok, %{id: user_id}} = User.follow(user, other_user)
 
       conn = get(conn, "/api/v1/accounts/#{other_user.id}/followers")
 
-      assert [%{"id" => id}] = json_response(conn, 200)
-      assert id == to_string(user.id)
-      assert_schema(json_response(conn, 200), "AccountsResponse", ApiSpec.spec())
+      assert [%{"id" => ^user_id}] = json_response_and_validate_schema(conn, 200)
     end
 
     test "getting followers, hide_followers", %{user: user, conn: conn} do
@@ -522,7 +490,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
 
       conn = get(conn, "/api/v1/accounts/#{other_user.id}/followers")
 
-      assert [] == json_response(conn, 200)
+      assert [] == json_response_and_validate_schema(conn, 200)
     end
 
     test "getting followers, hide_followers, same user requesting" do
@@ -536,40 +504,31 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
         |> assign(:token, insert(:oauth_token, user: other_user, scopes: ["read:accounts"]))
         |> get("/api/v1/accounts/#{other_user.id}/followers")
 
-      refute [] == json_response(conn, 200)
-      assert_schema(json_response(conn, 200), "AccountsResponse", ApiSpec.spec())
+      refute [] == json_response_and_validate_schema(conn, 200)
     end
 
     test "getting followers, pagination", %{user: user, conn: conn} do
-      follower1 = insert(:user)
-      follower2 = insert(:user)
-      follower3 = insert(:user)
-      {:ok, _} = User.follow(follower1, user)
-      {:ok, _} = User.follow(follower2, user)
-      {:ok, _} = User.follow(follower3, user)
+      {:ok, %User{id: follower1_id}} = :user |> insert() |> User.follow(user)
+      {:ok, %User{id: follower2_id}} = :user |> insert() |> User.follow(user)
+      {:ok, %User{id: follower3_id}} = :user |> insert() |> User.follow(user)
 
-      res_conn = get(conn, "/api/v1/accounts/#{user.id}/followers?since_id=#{follower1.id}")
+      assert [%{"id" => ^follower3_id}, %{"id" => ^follower2_id}] =
+               conn
+               |> get("/api/v1/accounts/#{user.id}/followers?since_id=#{follower1_id}")
+               |> json_response_and_validate_schema(200)
 
-      assert [%{"id" => id3}, %{"id" => id2}] = json_response(res_conn, 200)
-      assert id3 == follower3.id
-      assert id2 == follower2.id
-      assert_schema(json_response(res_conn, 200), "AccountsResponse", ApiSpec.spec())
+      assert [%{"id" => ^follower2_id}, %{"id" => ^follower1_id}] =
+               conn
+               |> get("/api/v1/accounts/#{user.id}/followers?max_id=#{follower3_id}")
+               |> json_response_and_validate_schema(200)
 
-      res_conn = get(conn, "/api/v1/accounts/#{user.id}/followers?max_id=#{follower3.id}")
+      res_conn = get(conn, "/api/v1/accounts/#{user.id}/followers?limit=1&max_id=#{follower3_id}")
 
-      assert [%{"id" => id2}, %{"id" => id1}] = json_response(res_conn, 200)
-      assert id2 == follower2.id
-      assert id1 == follower1.id
-
-      res_conn = get(conn, "/api/v1/accounts/#{user.id}/followers?limit=1&max_id=#{follower3.id}")
-
-      assert [%{"id" => id2}] = json_response(res_conn, 200)
-      assert id2 == follower2.id
+      assert [%{"id" => ^follower2_id}] = json_response_and_validate_schema(res_conn, 200)
 
       assert [link_header] = get_resp_header(res_conn, "link")
-      assert link_header =~ ~r/min_id=#{follower2.id}/
-      assert link_header =~ ~r/max_id=#{follower2.id}/
-      assert_schema(json_response(res_conn, 200), "AccountsResponse", ApiSpec.spec())
+      assert link_header =~ ~r/min_id=#{follower2_id}/
+      assert link_header =~ ~r/max_id=#{follower2_id}/
     end
   end
 
@@ -582,9 +541,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
 
       conn = get(conn, "/api/v1/accounts/#{user.id}/following")
 
-      assert [%{"id" => id}] = json_response(conn, 200)
+      assert [%{"id" => id}] = json_response_and_validate_schema(conn, 200)
       assert id == to_string(other_user.id)
-      assert_schema(json_response(conn, 200), "AccountsResponse", ApiSpec.spec())
     end
 
     test "getting following, hide_follows, other user requesting" do
@@ -598,8 +556,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
         |> assign(:token, insert(:oauth_token, user: other_user, scopes: ["read:accounts"]))
         |> get("/api/v1/accounts/#{user.id}/following")
 
-      assert [] == json_response(conn, 200)
-      assert_schema(json_response(conn, 200), "AccountsResponse", ApiSpec.spec())
+      assert [] == json_response_and_validate_schema(conn, 200)
     end
 
     test "getting following, hide_follows, same user requesting" do
@@ -613,7 +570,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
         |> assign(:token, insert(:oauth_token, user: user, scopes: ["read:accounts"]))
         |> get("/api/v1/accounts/#{user.id}/following")
 
-      refute [] == json_response(conn, 200)
+      refute [] == json_response_and_validate_schema(conn, 200)
     end
 
     test "getting following, pagination", %{user: user, conn: conn} do
@@ -626,28 +583,25 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
 
       res_conn = get(conn, "/api/v1/accounts/#{user.id}/following?since_id=#{following1.id}")
 
-      assert [%{"id" => id3}, %{"id" => id2}] = json_response(res_conn, 200)
+      assert [%{"id" => id3}, %{"id" => id2}] = json_response_and_validate_schema(res_conn, 200)
       assert id3 == following3.id
       assert id2 == following2.id
-      assert_schema(json_response(res_conn, 200), "AccountsResponse", ApiSpec.spec())
 
       res_conn = get(conn, "/api/v1/accounts/#{user.id}/following?max_id=#{following3.id}")
 
-      assert [%{"id" => id2}, %{"id" => id1}] = json_response(res_conn, 200)
+      assert [%{"id" => id2}, %{"id" => id1}] = json_response_and_validate_schema(res_conn, 200)
       assert id2 == following2.id
       assert id1 == following1.id
-      assert_schema(json_response(res_conn, 200), "AccountsResponse", ApiSpec.spec())
 
       res_conn =
         get(conn, "/api/v1/accounts/#{user.id}/following?limit=1&max_id=#{following3.id}")
 
-      assert [%{"id" => id2}] = json_response(res_conn, 200)
+      assert [%{"id" => id2}] = json_response_and_validate_schema(res_conn, 200)
       assert id2 == following2.id
 
       assert [link_header] = get_resp_header(res_conn, "link")
       assert link_header =~ ~r/min_id=#{following2.id}/
       assert link_header =~ ~r/max_id=#{following2.id}/
-      assert_schema(json_response(res_conn, 200), "AccountsResponse", ApiSpec.spec())
     end
   end
 
@@ -655,40 +609,37 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
     setup do: oauth_access(["follow"])
 
     test "following / unfollowing a user", %{conn: conn} do
-      other_user = insert(:user)
+      %{id: other_user_id, nickname: other_user_nickname} = insert(:user)
 
-      ret_conn = post(conn, "/api/v1/accounts/#{other_user.id}/follow")
+      assert %{"id" => _id, "following" => true} =
+               conn
+               |> post("/api/v1/accounts/#{other_user_id}/follow")
+               |> json_response_and_validate_schema(200)
 
-      assert %{"id" => _id, "following" => true} = json_response(ret_conn, 200)
-      assert_schema(json_response(ret_conn, 200), "AccountRelationship", ApiSpec.spec())
+      assert %{"id" => _id, "following" => false} =
+               conn
+               |> post("/api/v1/accounts/#{other_user_id}/unfollow")
+               |> json_response_and_validate_schema(200)
 
-      ret_conn = post(conn, "/api/v1/accounts/#{other_user.id}/unfollow")
-
-      assert %{"id" => _id, "following" => false} = json_response(ret_conn, 200)
-      assert_schema(json_response(ret_conn, 200), "AccountRelationship", ApiSpec.spec())
-
-      conn =
-        conn
-        |> put_req_header("content-type", "application/json")
-        |> post("/api/v1/follows", %{"uri" => other_user.nickname})
-
-      assert %{"id" => id} = json_response(conn, 200)
-      assert id == to_string(other_user.id)
-      assert_schema(json_response(conn, 200), "Account", ApiSpec.spec())
+      assert %{"id" => ^other_user_id} =
+               conn
+               |> put_req_header("content-type", "application/json")
+               |> post("/api/v1/follows", %{"uri" => other_user_nickname})
+               |> json_response_and_validate_schema(200)
     end
 
     test "cancelling follow request", %{conn: conn} do
       %{id: other_user_id} = insert(:user, %{locked: true})
 
-      resp = conn |> post("/api/v1/accounts/#{other_user_id}/follow") |> json_response(:ok)
+      assert %{"id" => ^other_user_id, "following" => false, "requested" => true} =
+               conn
+               |> post("/api/v1/accounts/#{other_user_id}/follow")
+               |> json_response_and_validate_schema(:ok)
 
-      assert %{"id" => ^other_user_id, "following" => false, "requested" => true} = resp
-      assert_schema(resp, "AccountRelationship", ApiSpec.spec())
-
-      resp = conn |> post("/api/v1/accounts/#{other_user_id}/unfollow") |> json_response(:ok)
-
-      assert %{"id" => ^other_user_id, "following" => false, "requested" => false} = resp
-      assert_schema(resp, "AccountRelationship", ApiSpec.spec())
+      assert %{"id" => ^other_user_id, "following" => false, "requested" => false} =
+               conn
+               |> post("/api/v1/accounts/#{other_user_id}/unfollow")
+               |> json_response_and_validate_schema(:ok)
     end
 
     test "following without reblogs" do
@@ -698,50 +649,53 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
 
       ret_conn = post(conn, "/api/v1/accounts/#{followed.id}/follow?reblogs=false")
 
-      assert %{"showing_reblogs" => false} = json_response(ret_conn, 200)
-      assert_schema(json_response(ret_conn, 200), "AccountRelationship", ApiSpec.spec())
+      assert %{"showing_reblogs" => false} = json_response_and_validate_schema(ret_conn, 200)
 
       {:ok, activity} = CommonAPI.post(other_user, %{"status" => "hey"})
-      {:ok, reblog, _} = CommonAPI.repeat(activity.id, followed)
+      {:ok, %{id: reblog_id}, _} = CommonAPI.repeat(activity.id, followed)
 
-      ret_conn = get(conn, "/api/v1/timelines/home")
+      assert [] ==
+               conn
+               |> get("/api/v1/timelines/home")
+               |> json_response(200)
 
-      assert [] == json_response(ret_conn, 200)
+      assert %{"showing_reblogs" => true} =
+               conn
+               |> post("/api/v1/accounts/#{followed.id}/follow?reblogs=true")
+               |> json_response_and_validate_schema(200)
 
-      ret_conn = post(conn, "/api/v1/accounts/#{followed.id}/follow?reblogs=true")
-
-      assert %{"showing_reblogs" => true} = json_response(ret_conn, 200)
-      assert_schema(json_response(ret_conn, 200), "AccountRelationship", ApiSpec.spec())
-
-      conn = get(conn, "/api/v1/timelines/home")
-
-      expected_activity_id = reblog.id
-      assert [%{"id" => ^expected_activity_id}] = json_response(conn, 200)
+      assert [%{"id" => ^reblog_id}] =
+               conn
+               |> get("/api/v1/timelines/home")
+               |> json_response(200)
     end
 
     test "following / unfollowing errors", %{user: user, conn: conn} do
       # self follow
       conn_res = post(conn, "/api/v1/accounts/#{user.id}/follow")
-      assert %{"error" => "Can not follow yourself"} = json_response(conn_res, 400)
+
+      assert %{"error" => "Can not follow yourself"} =
+               json_response_and_validate_schema(conn_res, 400)
 
       # self unfollow
       user = User.get_cached_by_id(user.id)
       conn_res = post(conn, "/api/v1/accounts/#{user.id}/unfollow")
-      assert %{"error" => "Can not unfollow yourself"} = json_response(conn_res, 400)
+
+      assert %{"error" => "Can not unfollow yourself"} =
+               json_response_and_validate_schema(conn_res, 400)
 
       # self follow via uri
       user = User.get_cached_by_id(user.id)
 
-      conn_res =
-        conn
-        |> put_req_header("content-type", "multipart/form-data")
-        |> post("/api/v1/follows", %{"uri" => user.nickname})
-
-      assert %{"error" => "Can not follow yourself"} = json_response(conn_res, 400)
+      assert %{"error" => "Can not follow yourself"} =
+               conn
+               |> put_req_header("content-type", "multipart/form-data")
+               |> post("/api/v1/follows", %{"uri" => user.nickname})
+               |> json_response_and_validate_schema(400)
 
       # follow non existing user
       conn_res = post(conn, "/api/v1/accounts/doesntexist/follow")
-      assert %{"error" => "Record not found"} = json_response(conn_res, 404)
+      assert %{"error" => "Record not found"} = json_response_and_validate_schema(conn_res, 404)
 
       # follow non existing user via uri
       conn_res =
@@ -749,11 +703,11 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
         |> put_req_header("content-type", "multipart/form-data")
         |> post("/api/v1/follows", %{"uri" => "doesntexist"})
 
-      assert %{"error" => "Record not found"} = json_response(conn_res, 404)
+      assert %{"error" => "Record not found"} = json_response_and_validate_schema(conn_res, 404)
 
       # unfollow non existing user
       conn_res = post(conn, "/api/v1/accounts/doesntexist/unfollow")
-      assert %{"error" => "Record not found"} = json_response(conn_res, 404)
+      assert %{"error" => "Record not found"} = json_response_and_validate_schema(conn_res, 404)
     end
   end
 
@@ -763,21 +717,16 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
     test "with notifications", %{conn: conn} do
       other_user = insert(:user)
 
-      ret_conn =
-        conn
-        |> put_req_header("content-type", "application/json")
-        |> post("/api/v1/accounts/#{other_user.id}/mute")
-
-      response = json_response(ret_conn, 200)
-
-      assert %{"id" => _id, "muting" => true, "muting_notifications" => true} = response
-      assert_schema(response, "AccountRelationship", ApiSpec.spec())
+      assert %{"id" => _id, "muting" => true, "muting_notifications" => true} =
+               conn
+               |> put_req_header("content-type", "application/json")
+               |> post("/api/v1/accounts/#{other_user.id}/mute")
+               |> json_response_and_validate_schema(200)
 
       conn = post(conn, "/api/v1/accounts/#{other_user.id}/unmute")
 
-      response = json_response(conn, 200)
-      assert %{"id" => _id, "muting" => false, "muting_notifications" => false} = response
-      assert_schema(response, "AccountRelationship", ApiSpec.spec())
+      assert %{"id" => _id, "muting" => false, "muting_notifications" => false} =
+               json_response_and_validate_schema(conn, 200)
     end
 
     test "without notifications", %{conn: conn} do
@@ -788,16 +737,13 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
         |> put_req_header("content-type", "multipart/form-data")
         |> post("/api/v1/accounts/#{other_user.id}/mute", %{"notifications" => "false"})
 
-      response = json_response(ret_conn, 200)
-
-      assert %{"id" => _id, "muting" => true, "muting_notifications" => false} = response
-      assert_schema(response, "AccountRelationship", ApiSpec.spec())
+      assert %{"id" => _id, "muting" => true, "muting_notifications" => false} =
+               json_response_and_validate_schema(ret_conn, 200)
 
       conn = post(conn, "/api/v1/accounts/#{other_user.id}/unmute")
 
-      response = json_response(conn, 200)
-      assert %{"id" => _id, "muting" => false, "muting_notifications" => false} = response
-      assert_schema(response, "AccountRelationship", ApiSpec.spec())
+      assert %{"id" => _id, "muting" => false, "muting_notifications" => false} =
+               json_response_and_validate_schema(conn, 200)
     end
   end
 
@@ -810,17 +756,13 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
       [conn: conn, user: user, activity: activity]
     end
 
-    test "returns pinned statuses", %{conn: conn, user: user, activity: activity} do
-      {:ok, _} = CommonAPI.pin(activity.id, user)
+    test "returns pinned statuses", %{conn: conn, user: user, activity: %{id: activity_id}} do
+      {:ok, _} = CommonAPI.pin(activity_id, user)
 
-      result =
-        conn
-        |> get("/api/v1/accounts/#{user.id}/statuses?pinned=true")
-        |> json_response(200)
-
-      id_str = to_string(activity.id)
-
-      assert [%{"id" => ^id_str, "pinned" => true}] = result
+      assert [%{"id" => ^activity_id, "pinned" => true}] =
+               conn
+               |> get("/api/v1/accounts/#{user.id}/statuses?pinned=true")
+               |> json_response_and_validate_schema(200)
     end
   end
 
@@ -830,13 +772,11 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
 
     ret_conn = post(conn, "/api/v1/accounts/#{other_user.id}/block")
 
-    assert %{"id" => _id, "blocking" => true} = json_response(ret_conn, 200)
-    assert_schema(json_response(ret_conn, 200), "AccountRelationship", ApiSpec.spec())
+    assert %{"id" => _id, "blocking" => true} = json_response_and_validate_schema(ret_conn, 200)
 
     conn = post(conn, "/api/v1/accounts/#{other_user.id}/unblock")
 
-    assert %{"id" => _id, "blocking" => false} = json_response(conn, 200)
-    assert_schema(json_response(ret_conn, 200), "AccountRelationship", ApiSpec.spec())
+    assert %{"id" => _id, "blocking" => false} = json_response_and_validate_schema(conn, 200)
   end
 
   describe "create account by app" do
@@ -863,15 +803,15 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
           scopes: "read, write, follow"
         })
 
-      %{
-        "client_id" => client_id,
-        "client_secret" => client_secret,
-        "id" => _,
-        "name" => "client_name",
-        "redirect_uri" => "urn:ietf:wg:oauth:2.0:oob",
-        "vapid_key" => _,
-        "website" => nil
-      } = json_response(conn, 200)
+      assert %{
+               "client_id" => client_id,
+               "client_secret" => client_secret,
+               "id" => _,
+               "name" => "client_name",
+               "redirect_uri" => "urn:ietf:wg:oauth:2.0:oob",
+               "vapid_key" => _,
+               "website" => nil
+             } = json_response_and_validate_schema(conn, 200)
 
       conn =
         post(conn, "/oauth/token", %{
@@ -906,7 +846,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
         "created_at" => _created_at,
         "scope" => _scope,
         "token_type" => "Bearer"
-      } = json_response(conn, 200)
+      } = json_response_and_validate_schema(conn, 200)
 
       token_from_db = Repo.get_by(Token, token: token)
       assert token_from_db
@@ -926,7 +866,9 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
         |> put_req_header("content-type", "application/json")
         |> post("/api/v1/accounts", valid_params)
 
-      assert json_response(res, 400) == %{"error" => "{\"email\":[\"has already been taken\"]}"}
+      assert json_response_and_validate_schema(res, 400) == %{
+               "error" => "{\"email\":[\"has already been taken\"]}"
+             }
     end
 
     test "returns bad_request if missing required params", %{
@@ -941,7 +883,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
         |> put_req_header("content-type", "application/json")
 
       res = post(conn, "/api/v1/accounts", valid_params)
-      assert json_response(res, 200)
+      assert json_response_and_validate_schema(res, 200)
 
       [{127, 0, 0, 1}, {127, 0, 0, 2}, {127, 0, 0, 3}, {127, 0, 0, 4}]
       |> Stream.zip(Map.delete(valid_params, :email))
@@ -950,7 +892,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
           conn
           |> Map.put(:remote_ip, ip)
           |> post("/api/v1/accounts", Map.delete(valid_params, attr))
-          |> json_response(400)
+          |> json_response_and_validate_schema(400)
 
         assert res == %{
                  "error" => "Missing field: #{attr}.",
@@ -983,14 +925,16 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
         |> Map.put(:remote_ip, {127, 0, 0, 5})
         |> post("/api/v1/accounts", Map.delete(valid_params, :email))
 
-      assert json_response(res, 400) == %{"error" => "Missing parameters"}
+      assert json_response_and_validate_schema(res, 400) == %{"error" => "Missing parameters"}
 
       res =
         conn
         |> Map.put(:remote_ip, {127, 0, 0, 6})
         |> post("/api/v1/accounts", Map.put(valid_params, :email, ""))
 
-      assert json_response(res, 400) == %{"error" => "{\"email\":[\"can't be blank\"]}"}
+      assert json_response_and_validate_schema(res, 400) == %{
+               "error" => "{\"email\":[\"can't be blank\"]}"
+             }
     end
 
     test "allow registration without an email", %{conn: conn, valid_params: valid_params} do
@@ -1003,7 +947,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
         |> Map.put(:remote_ip, {127, 0, 0, 7})
         |> post("/api/v1/accounts", Map.delete(valid_params, :email))
 
-      assert json_response(res, 200)
+      assert json_response_and_validate_schema(res, 200)
     end
 
     test "allow registration with an empty email", %{conn: conn, valid_params: valid_params} do
@@ -1016,7 +960,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
         |> Map.put(:remote_ip, {127, 0, 0, 8})
         |> post("/api/v1/accounts", Map.put(valid_params, :email, ""))
 
-      assert json_response(res, 200)
+      assert json_response_and_validate_schema(res, 200)
     end
 
     test "returns forbidden if token is invalid", %{conn: conn, valid_params: valid_params} do
@@ -1026,7 +970,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
         |> put_req_header("content-type", "multipart/form-data")
         |> post("/api/v1/accounts", valid_params)
 
-      assert json_response(res, 403) == %{"error" => "Invalid credentials"}
+      assert json_response_and_validate_schema(res, 403) == %{"error" => "Invalid credentials"}
     end
 
     test "registration from trusted app" do
@@ -1056,7 +1000,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
           password: "some_password",
           confirm: "some_password"
         })
-        |> json_response(200)
+        |> json_response_and_validate_schema(200)
 
       assert %{
                "access_token" => access_token,
@@ -1069,7 +1013,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
         build_conn()
         |> Plug.Conn.put_req_header("authorization", "Bearer " <> access_token)
         |> get("/api/v1/accounts/verify_credentials")
-        |> json_response(200)
+        |> json_response_and_validate_schema(200)
 
       assert %{
                "acct" => "Lain",
@@ -1125,7 +1069,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
           "created_at" => _created_at,
           "scope" => _scope,
           "token_type" => "Bearer"
-        } = json_response(conn, 200)
+        } = json_response_and_validate_schema(conn, 200)
 
         token_from_db = Repo.get_by(Token, token: token)
         assert token_from_db
@@ -1143,7 +1087,9 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
           agreement: true
         })
 
-      assert json_response(conn, :too_many_requests) == %{"error" => "Throttled"}
+      assert json_response_and_validate_schema(conn, :too_many_requests) == %{
+               "error" => "Throttled"
+             }
     end
   end
 
@@ -1151,16 +1097,13 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
     test "returns lists to which the account belongs" do
       %{user: user, conn: conn} = oauth_access(["read:lists"])
       other_user = insert(:user)
-      assert {:ok, %Pleroma.List{} = list} = Pleroma.List.create("Test List", user)
+      assert {:ok, %Pleroma.List{id: list_id} = list} = Pleroma.List.create("Test List", user)
       {:ok, %{following: _following}} = Pleroma.List.follow(list, other_user)
 
-      res =
-        conn
-        |> get("/api/v1/accounts/#{other_user.id}/lists")
-        |> json_response(200)
-
-      assert res == [%{"id" => to_string(list.id), "title" => "Test List"}]
-      assert_schema(res, "ListsResponse", ApiSpec.spec())
+      assert [%{"id" => list_id, "title" => "Test List"}] =
+               conn
+               |> get("/api/v1/accounts/#{other_user.id}/lists")
+               |> json_response_and_validate_schema(200)
     end
   end
 
@@ -1169,7 +1112,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
       %{user: user, conn: conn} = oauth_access(["read:accounts"])
       conn = get(conn, "/api/v1/accounts/verify_credentials")
 
-      response = json_response(conn, 200)
+      response = json_response_and_validate_schema(conn, 200)
 
       assert %{"id" => id, "source" => %{"privacy" => "public"}} = response
       assert response["pleroma"]["chat_token"]
@@ -1182,7 +1125,9 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
 
       conn = get(conn, "/api/v1/accounts/verify_credentials")
 
-      assert %{"id" => id, "source" => %{"privacy" => "unlisted"}} = json_response(conn, 200)
+      assert %{"id" => id, "source" => %{"privacy" => "unlisted"}} =
+               json_response_and_validate_schema(conn, 200)
+
       assert id == to_string(user.id)
     end
 
@@ -1192,7 +1137,9 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
 
       conn = get(conn, "/api/v1/accounts/verify_credentials")
 
-      assert %{"id" => id, "source" => %{"privacy" => "private"}} = json_response(conn, 200)
+      assert %{"id" => id, "source" => %{"privacy" => "private"}} =
+               json_response_and_validate_schema(conn, 200)
+
       assert id == to_string(user.id)
     end
   end
@@ -1207,18 +1154,18 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
       assert [%{"id" => ^other_user_id}] =
                conn
                |> get("/api/v1/accounts/relationships?id=#{other_user.id}")
-               |> json_response(200)
+               |> json_response_and_validate_schema(200)
 
       assert [%{"id" => ^other_user_id}] =
                conn
                |> get("/api/v1/accounts/relationships?id[]=#{other_user.id}")
-               |> json_response(200)
+               |> json_response_and_validate_schema(200)
     end
 
     test "returns an empty list on a bad request", %{conn: conn} do
       conn = get(conn, "/api/v1/accounts/relationships", %{})
 
-      assert [] = json_response(conn, 200)
+      assert [] = json_response_and_validate_schema(conn, 200)
     end
   end
 
@@ -1231,8 +1178,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
     conn = get(conn, "/api/v1/mutes")
 
     other_user_id = to_string(other_user.id)
-    assert [%{"id" => ^other_user_id}] = json_response(conn, 200)
-    assert_schema(json_response(conn, 200), "AccountsResponse", ApiSpec.spec())
+    assert [%{"id" => ^other_user_id}] = json_response_and_validate_schema(conn, 200)
   end
 
   test "getting a list of blocks" do
@@ -1247,7 +1193,6 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
       |> get("/api/v1/blocks")
 
     other_user_id = to_string(other_user.id)
-    assert [%{"id" => ^other_user_id}] = json_response(conn, 200)
-    assert_schema(json_response(conn, 200), "AccountsResponse", ApiSpec.spec())
+    assert [%{"id" => ^other_user_id}] = json_response_and_validate_schema(conn, 200)
   end
 end

From cc1e2e8d0f5fa27d051a0a21740a8052b95ce1a5 Mon Sep 17 00:00:00 2001
From: fence <fence@desu-mail.moe>
Date: Mon, 27 Apr 2020 19:11:03 +0200
Subject: [PATCH 36/40] requested changes to mongoose_im_controller.ex

---
 .../web/mongooseim/mongoose_im_controller.ex  | 45 ++++++-------------
 1 file changed, 13 insertions(+), 32 deletions(-)

diff --git a/lib/pleroma/web/mongooseim/mongoose_im_controller.ex b/lib/pleroma/web/mongooseim/mongoose_im_controller.ex
index c15b4bfb8..7123153c5 100644
--- a/lib/pleroma/web/mongooseim/mongoose_im_controller.ex
+++ b/lib/pleroma/web/mongooseim/mongoose_im_controller.ex
@@ -14,7 +14,7 @@ defmodule Pleroma.Web.MongooseIM.MongooseIMController do
   plug(RateLimiter, [name: :authentication, params: ["user"]] when action == :check_password)
 
   def user_exists(conn, %{"user" => username}) do
-    with %User{} <- Repo.get_by(User, nickname: username, local: true) do
+    with %User{} <- Repo.get_by(User, nickname: username, local: true, deactivated: false) do
       conn
       |> json(true)
     else
@@ -26,41 +26,22 @@ defmodule Pleroma.Web.MongooseIM.MongooseIMController do
   end
 
   def check_password(conn, %{"user" => username, "pass" => password}) do
-    user = Repo.get_by(User, nickname: username, local: true)
-    
-    state = case user do
-      nil -> nil
-      _ -> User.account_status(user)
-    end
-
-    case state do
-      :deactivated ->
+    with %User{password_hash: password_hash, deactivated: false} <-
+           Repo.get_by(User, nickname: username, local: true),
+         true <- Pbkdf2.checkpw(password, password_hash) do
+      conn
+      |> json(true)
+    else
+      false ->
         conn
-        |> put_status(:not_found)
-        |> json(false)
-
-      :confirmation_pending ->
-        conn
-        |> put_status(:not_found)
+        |> put_status(:forbidden)
         |> json(false)
 
       _ ->
-        with %User{password_hash: password_hash} <-
-               user,
-             true <- Pbkdf2.checkpw(password, password_hash) do
-          conn
-          |> json(true)
-        else
-          false ->
-            conn
-            |> put_status(:forbidden)
-            |> json(false)
-
-          _ ->
-            conn
-            |> put_status(:not_found)
-            |> json(false)
-        end
+        conn
+        |> put_status(:not_found)
+        |> json(false)
     end
   end
 end
+

From 935ca2c1329fd4b4f2b40a5ed7f2c1fa5f95b9bd Mon Sep 17 00:00:00 2001
From: fence <fence@desu-mail.moe>
Date: Mon, 27 Apr 2020 19:16:05 +0200
Subject: [PATCH 37/40] requested changes to mongoose test

---
 test/web/mongooseim/mongoose_im_controller_test.exs | 12 ++++++++++--
 1 file changed, 10 insertions(+), 2 deletions(-)

diff --git a/test/web/mongooseim/mongoose_im_controller_test.exs b/test/web/mongooseim/mongoose_im_controller_test.exs
index 5987111e5..2b4d124af 100644
--- a/test/web/mongooseim/mongoose_im_controller_test.exs
+++ b/test/web/mongooseim/mongoose_im_controller_test.exs
@@ -9,6 +9,7 @@ defmodule Pleroma.Web.MongooseIMController do
   test "/user_exists", %{conn: conn} do
     _user = insert(:user, nickname: "lain")
     _remote_user = insert(:user, nickname: "alice", local: false)
+    _deactivated_user = insert(:user, nickname: "konata", deactivated: true)
 
     res =
       conn
@@ -30,11 +31,18 @@ defmodule Pleroma.Web.MongooseIMController do
       |> json_response(404)
 
     assert res == false
+
+    res =
+      conn
+      |> get(mongoose_im_path(conn, :user_exists), user: "konata")
+      |> json_response(404)
+
+    assert res == false
   end
 
   test "/check_password", %{conn: conn} do
     user = insert(:user, password_hash: Comeonin.Pbkdf2.hashpwsalt("cool"))
-    _deactivated_user = insert(:user, nickname: "konata", local: false, deactivated: true)
+    _deactivated_user = insert(:user, nickname: "konata", deactivated: true)
 
     res =
       conn
@@ -52,7 +60,7 @@ defmodule Pleroma.Web.MongooseIMController do
 
     res =
       conn
-      |> get(mongoose_im_path(conn, :check_password), user: "konata", pass: "1337")
+      |> get(mongoose_im_path(conn, :check_password), user: "konata", pass: "cool")
       |> json_response(404)
 
     assert res == false

From d607b4d84002ff14f51713f1ac74a4971e2dffac Mon Sep 17 00:00:00 2001
From: fence <fence@desu-mail.moe>
Date: Mon, 27 Apr 2020 19:32:58 +0200
Subject: [PATCH 38/40] mongooseim test: explicitly set password for the
 deactivated used

---
 test/web/mongooseim/mongoose_im_controller_test.exs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/test/web/mongooseim/mongoose_im_controller_test.exs b/test/web/mongooseim/mongoose_im_controller_test.exs
index 2b4d124af..d17f8dbb4 100644
--- a/test/web/mongooseim/mongoose_im_controller_test.exs
+++ b/test/web/mongooseim/mongoose_im_controller_test.exs
@@ -42,7 +42,7 @@ defmodule Pleroma.Web.MongooseIMController do
 
   test "/check_password", %{conn: conn} do
     user = insert(:user, password_hash: Comeonin.Pbkdf2.hashpwsalt("cool"))
-    _deactivated_user = insert(:user, nickname: "konata", deactivated: true)
+    _deactivated_user = insert(:user, nickname: "konata", deactivated: true, password_hash: Comeonin.Pbkdf2.hashpwsalt("cool"))
 
     res =
       conn

From dda65f7799e9dfa2e7b87389848eeee10993a858 Mon Sep 17 00:00:00 2001
From: Egor Kislitsyn <egor@kislitsyn.com>
Date: Mon, 27 Apr 2020 22:55:05 +0400
Subject: [PATCH 39/40] Move single used schemas to operation schema

---
 .../api_spec/operations/account_operation.ex  | 376 +++++++++++++++++-
 .../operations/custom_emoji_operation.ex      |  35 +-
 lib/pleroma/web/api_spec/schemas/account.ex   |   4 +-
 .../schemas/account_create_request.ex         |  60 ---
 .../schemas/account_create_response.ex        |  27 --
 .../schemas/account_field_attribute.ex        |  24 --
 .../schemas/account_follows_request.ex        |  18 -
 .../api_spec/schemas/account_mute_request.ex  |  24 --
 .../schemas/account_relationships_response.ex |  58 ---
 .../account_update_credentials_request.ex     | 125 ------
 .../web/api_spec/schemas/accounts_response.ex |  13 -
 .../schemas/{list.ex => api_error.ex}         |  14 +-
 .../web/api_spec/schemas/custom_emoji.ex      |  30 --
 .../schemas/{account_emoji.ex => emoji.ex}    |   6 +-
 .../web/api_spec/schemas/lists_response.ex    |  16 -
 lib/pleroma/web/api_spec/schemas/poll.ex      |   4 +-
 lib/pleroma/web/api_spec/schemas/status.ex    |   4 +-
 .../web/api_spec/schemas/statuses_response.ex |  13 -
 .../controllers/account_controller.ex         |  11 +-
 test/web/api_spec/account_operation_test.exs  | 141 -------
 .../custom_emoji_controller_test.exs          |   3 -
 21 files changed, 402 insertions(+), 604 deletions(-)
 delete mode 100644 lib/pleroma/web/api_spec/schemas/account_create_request.ex
 delete mode 100644 lib/pleroma/web/api_spec/schemas/account_create_response.ex
 delete mode 100644 lib/pleroma/web/api_spec/schemas/account_field_attribute.ex
 delete mode 100644 lib/pleroma/web/api_spec/schemas/account_follows_request.ex
 delete mode 100644 lib/pleroma/web/api_spec/schemas/account_mute_request.ex
 delete mode 100644 lib/pleroma/web/api_spec/schemas/account_relationships_response.ex
 delete mode 100644 lib/pleroma/web/api_spec/schemas/account_update_credentials_request.ex
 delete mode 100644 lib/pleroma/web/api_spec/schemas/accounts_response.ex
 rename lib/pleroma/web/api_spec/schemas/{list.ex => api_error.ex} (52%)
 delete mode 100644 lib/pleroma/web/api_spec/schemas/custom_emoji.ex
 rename lib/pleroma/web/api_spec/schemas/{account_emoji.ex => emoji.ex} (85%)
 delete mode 100644 lib/pleroma/web/api_spec/schemas/lists_response.ex
 delete mode 100644 lib/pleroma/web/api_spec/schemas/statuses_response.ex
 delete mode 100644 test/web/api_spec/account_operation_test.exs

diff --git a/lib/pleroma/web/api_spec/operations/account_operation.ex b/lib/pleroma/web/api_spec/operations/account_operation.ex
index bf8d21059..2efe6e901 100644
--- a/lib/pleroma/web/api_spec/operations/account_operation.ex
+++ b/lib/pleroma/web/api_spec/operations/account_operation.ex
@@ -7,18 +7,11 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
   alias OpenApiSpex.Reference
   alias OpenApiSpex.Schema
   alias Pleroma.Web.ApiSpec.Schemas.Account
-  alias Pleroma.Web.ApiSpec.Schemas.ApiError
-  alias Pleroma.Web.ApiSpec.Schemas.AccountCreateRequest
-  alias Pleroma.Web.ApiSpec.Schemas.AccountCreateResponse
-  alias Pleroma.Web.ApiSpec.Schemas.AccountFollowsRequest
-  alias Pleroma.Web.ApiSpec.Schemas.AccountMuteRequest
   alias Pleroma.Web.ApiSpec.Schemas.AccountRelationship
-  alias Pleroma.Web.ApiSpec.Schemas.AccountRelationshipsResponse
-  alias Pleroma.Web.ApiSpec.Schemas.AccountsResponse
-  alias Pleroma.Web.ApiSpec.Schemas.AccountUpdateCredentialsRequest
+  alias Pleroma.Web.ApiSpec.Schemas.ActorType
+  alias Pleroma.Web.ApiSpec.Schemas.ApiError
   alias Pleroma.Web.ApiSpec.Schemas.BooleanLike
-  alias Pleroma.Web.ApiSpec.Schemas.ListsResponse
-  alias Pleroma.Web.ApiSpec.Schemas.StatusesResponse
+  alias Pleroma.Web.ApiSpec.Schemas.Status
   alias Pleroma.Web.ApiSpec.Schemas.VisibilityScope
 
   import Pleroma.Web.ApiSpec.Helpers
@@ -37,9 +30,9 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
       description:
         "Creates a user and account records. Returns an account access token for the app that initiated the request. The app should save this token for later, and should wait for the user to confirm their account by clicking a link in their email inbox.",
       operationId: "AccountController.create",
-      requestBody: request_body("Parameters", AccountCreateRequest, required: true),
+      requestBody: request_body("Parameters", create_request(), required: true),
       responses: %{
-        200 => Operation.response("Account", "application/json", AccountCreateResponse),
+        200 => Operation.response("Account", "application/json", create_response()),
         400 => Operation.response("Error", "application/json", ApiError),
         403 => Operation.response("Error", "application/json", ApiError),
         429 => Operation.response("Error", "application/json", ApiError)
@@ -67,7 +60,7 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
       description: "Update the user's display and preferences.",
       operationId: "AccountController.update_credentials",
       security: [%{"oAuth" => ["write:accounts"]}],
-      requestBody: request_body("Parameters", AccountUpdateCredentialsRequest, required: true),
+      requestBody: request_body("Parameters", update_creadentials_request(), required: true),
       responses: %{
         200 => Operation.response("Account", "application/json", Account),
         403 => Operation.response("Error", "application/json", ApiError)
@@ -94,7 +87,7 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
         )
       ],
       responses: %{
-        200 => Operation.response("Account", "application/json", AccountRelationshipsResponse)
+        200 => Operation.response("Account", "application/json", array_of_relationships())
       }
     }
   end
@@ -146,7 +139,7 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
           )
         ] ++ pagination_params(),
       responses: %{
-        200 => Operation.response("Statuses", "application/json", StatusesResponse),
+        200 => Operation.response("Statuses", "application/json", array_of_statuses()),
         404 => Operation.response("Error", "application/json", ApiError)
       }
     }
@@ -163,7 +156,7 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
       parameters:
         [%Reference{"$ref": "#/components/parameters/accountIdOrNickname"}] ++ pagination_params(),
       responses: %{
-        200 => Operation.response("Accounts", "application/json", AccountsResponse)
+        200 => Operation.response("Accounts", "application/json", array_of_accounts())
       }
     }
   end
@@ -178,7 +171,7 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
         "Accounts which the given account is following, if network is not hidden by the account owner.",
       parameters:
         [%Reference{"$ref": "#/components/parameters/accountIdOrNickname"}] ++ pagination_params(),
-      responses: %{200 => Operation.response("Accounts", "application/json", AccountsResponse)}
+      responses: %{200 => Operation.response("Accounts", "application/json", array_of_accounts())}
     }
   end
 
@@ -190,7 +183,7 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
       security: [%{"oAuth" => ["read:lists"]}],
       description: "User lists that you have added this account to.",
       parameters: [%Reference{"$ref": "#/components/parameters/accountIdOrNickname"}],
-      responses: %{200 => Operation.response("Lists", "application/json", ListsResponse)}
+      responses: %{200 => Operation.response("Lists", "application/json", array_of_lists())}
     }
   end
 
@@ -240,7 +233,7 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
       summary: "Mute",
       operationId: "AccountController.mute",
       security: [%{"oAuth" => ["follow", "write:mutes"]}],
-      requestBody: request_body("Parameters", AccountMuteRequest),
+      requestBody: request_body("Parameters", mute_request()),
       description:
         "Mute the given account. Clients should filter statuses and notifications from this account, if received (e.g. due to a boost in the Home timeline).",
       parameters: [
@@ -307,7 +300,7 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
       summary: "Follows",
       operationId: "AccountController.follows",
       security: [%{"oAuth" => ["follow", "write:follows"]}],
-      requestBody: request_body("Parameters", AccountFollowsRequest, required: true),
+      requestBody: request_body("Parameters", follows_request(), required: true),
       responses: %{
         200 => Operation.response("Account", "application/json", AccountRelationship),
         400 => Operation.response("Error", "application/json", ApiError),
@@ -324,7 +317,7 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
       description: "Accounts the user has muted.",
       security: [%{"oAuth" => ["follow", "read:mutes"]}],
       responses: %{
-        200 => Operation.response("Accounts", "application/json", AccountsResponse)
+        200 => Operation.response("Accounts", "application/json", array_of_accounts())
       }
     }
   end
@@ -337,7 +330,7 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
       description: "View your blocks. See also accounts/:id/{block,unblock}",
       security: [%{"oAuth" => ["read:blocks"]}],
       responses: %{
-        200 => Operation.response("Accounts", "application/json", AccountsResponse)
+        200 => Operation.response("Accounts", "application/json", array_of_accounts())
       }
     }
   end
@@ -366,4 +359,343 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
       }
     }
   end
+
+  defp create_request do
+    %Schema{
+      title: "AccountCreateRequest",
+      description: "POST body for creating an account",
+      type: :object,
+      properties: %{
+        reason: %Schema{
+          type: :string,
+          description:
+            "Text that will be reviewed by moderators if registrations require manual approval"
+        },
+        username: %Schema{type: :string, description: "The desired username for the account"},
+        email: %Schema{
+          type: :string,
+          description:
+            "The email address to be used for login. Required when `account_activation_required` is enabled.",
+          format: :email
+        },
+        password: %Schema{
+          type: :string,
+          description: "The password to be used for login",
+          format: :password
+        },
+        agreement: %Schema{
+          type: :boolean,
+          description:
+            "Whether the user agrees to the local rules, terms, and policies. These should be presented to the user in order to allow them to consent before setting this parameter to TRUE."
+        },
+        locale: %Schema{
+          type: :string,
+          description: "The language of the confirmation email that will be sent"
+        },
+        # Pleroma-specific properties:
+        fullname: %Schema{type: :string, description: "Full name"},
+        bio: %Schema{type: :string, description: "Bio", default: ""},
+        captcha_solution: %Schema{
+          type: :string,
+          description: "Provider-specific captcha solution"
+        },
+        captcha_token: %Schema{type: :string, description: "Provider-specific captcha token"},
+        captcha_answer_data: %Schema{type: :string, description: "Provider-specific captcha data"},
+        token: %Schema{
+          type: :string,
+          description: "Invite token required when the registrations aren't public"
+        }
+      },
+      required: [:username, :password, :agreement],
+      example: %{
+        "username" => "cofe",
+        "email" => "cofe@example.com",
+        "password" => "secret",
+        "agreement" => "true",
+        "bio" => "☕️"
+      }
+    }
+  end
+
+  defp create_response do
+    %Schema{
+      title: "AccountCreateResponse",
+      description: "Response schema for an account",
+      type: :object,
+      properties: %{
+        token_type: %Schema{type: :string},
+        access_token: %Schema{type: :string},
+        scope: %Schema{type: :array, items: %Schema{type: :string}},
+        created_at: %Schema{type: :integer, format: :"date-time"}
+      },
+      example: %{
+        "access_token" => "i9hAVVzGld86Pl5JtLtizKoXVvtTlSCJvwaugCxvZzk",
+        "created_at" => 1_585_918_714,
+        "scope" => ["read", "write", "follow", "push"],
+        "token_type" => "Bearer"
+      }
+    }
+  end
+
+  defp update_creadentials_request do
+    %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: attribute_field()},
+            %Schema{type: :object, additionalProperties: %Schema{type: attribute_field()}}
+          ]
+        },
+        # 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: VisibilityScope,
+        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: ActorType
+      },
+      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
+
+  defp array_of_accounts do
+    %Schema{
+      title: "ArrayOfAccounts",
+      type: :array,
+      items: Account
+    }
+  end
+
+  defp array_of_relationships do
+    %Schema{
+      title: "ArrayOfRelationships",
+      description: "Response schema for account relationships",
+      type: :array,
+      items: AccountRelationship,
+      example: [
+        %{
+          "id" => "1",
+          "following" => true,
+          "showing_reblogs" => true,
+          "followed_by" => true,
+          "blocking" => false,
+          "blocked_by" => true,
+          "muting" => false,
+          "muting_notifications" => false,
+          "requested" => false,
+          "domain_blocking" => false,
+          "subscribing" => false,
+          "endorsed" => true
+        },
+        %{
+          "id" => "2",
+          "following" => true,
+          "showing_reblogs" => true,
+          "followed_by" => true,
+          "blocking" => false,
+          "blocked_by" => true,
+          "muting" => true,
+          "muting_notifications" => false,
+          "requested" => true,
+          "domain_blocking" => false,
+          "subscribing" => false,
+          "endorsed" => false
+        },
+        %{
+          "id" => "3",
+          "following" => true,
+          "showing_reblogs" => true,
+          "followed_by" => true,
+          "blocking" => true,
+          "blocked_by" => false,
+          "muting" => true,
+          "muting_notifications" => false,
+          "requested" => false,
+          "domain_blocking" => true,
+          "subscribing" => true,
+          "endorsed" => false
+        }
+      ]
+    }
+  end
+
+  defp follows_request do
+    %Schema{
+      title: "AccountFollowsRequest",
+      description: "POST body for muting an account",
+      type: :object,
+      properties: %{
+        uri: %Schema{type: :string, format: :uri}
+      },
+      required: [:uri]
+    }
+  end
+
+  defp mute_request do
+    %Schema{
+      title: "AccountMuteRequest",
+      description: "POST body for muting an account",
+      type: :object,
+      properties: %{
+        notifications: %Schema{
+          type: :boolean,
+          description: "Mute notifications in addition to statuses? Defaults to true.",
+          default: true
+        }
+      },
+      example: %{
+        "notifications" => true
+      }
+    }
+  end
+
+  defp list do
+    %Schema{
+      title: "List",
+      description: "Response schema for a list",
+      type: :object,
+      properties: %{
+        id: %Schema{type: :string},
+        title: %Schema{type: :string}
+      },
+      example: %{
+        "id" => "123",
+        "title" => "my list"
+      }
+    }
+  end
+
+  defp array_of_lists do
+    %Schema{
+      title: "ArrayOfLists",
+      description: "Response schema for lists",
+      type: :array,
+      items: list(),
+      example: [
+        %{"id" => "123", "title" => "my list"},
+        %{"id" => "1337", "title" => "anotehr list"}
+      ]
+    }
+  end
+
+  defp array_of_statuses do
+    %Schema{
+      title: "ArrayOfStatuses",
+      type: :array,
+      items: Status
+    }
+  end
+
+  defp attribute_field do
+    %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: %{
+        "name" => "Website",
+        "value" => "https://pleroma.com"
+      }
+    }
+  end
 end
diff --git a/lib/pleroma/web/api_spec/operations/custom_emoji_operation.ex b/lib/pleroma/web/api_spec/operations/custom_emoji_operation.ex
index a117fe460..2f812ac77 100644
--- a/lib/pleroma/web/api_spec/operations/custom_emoji_operation.ex
+++ b/lib/pleroma/web/api_spec/operations/custom_emoji_operation.ex
@@ -5,7 +5,7 @@
 defmodule Pleroma.Web.ApiSpec.CustomEmojiOperation do
   alias OpenApiSpex.Operation
   alias OpenApiSpex.Schema
-  alias Pleroma.Web.ApiSpec.Schemas.CustomEmoji
+  alias Pleroma.Web.ApiSpec.Schemas.Emoji
 
   def open_api_operation(action) do
     operation = String.to_existing_atom("#{action}_operation")
@@ -19,17 +19,17 @@ defmodule Pleroma.Web.ApiSpec.CustomEmojiOperation do
       description: "Returns custom emojis that are available on the server.",
       operationId: "CustomEmojiController.index",
       responses: %{
-        200 => Operation.response("Custom Emojis", "application/json", custom_emojis_resposnse())
+        200 => Operation.response("Custom Emojis", "application/json", resposnse())
       }
     }
   end
 
-  defp custom_emojis_resposnse do
+  defp resposnse do
     %Schema{
       title: "CustomEmojisResponse",
       description: "Response schema for custom emojis",
       type: :array,
-      items: CustomEmoji,
+      items: custom_emoji(),
       example: [
         %{
           "category" => "Fun",
@@ -58,4 +58,31 @@ defmodule Pleroma.Web.ApiSpec.CustomEmojiOperation do
       ]
     }
   end
+
+  defp custom_emoji do
+    %Schema{
+      title: "CustomEmoji",
+      description: "Schema for a CustomEmoji",
+      allOf: [
+        Emoji,
+        %Schema{
+          type: :object,
+          properties: %{
+            category: %Schema{type: :string},
+            tags: %Schema{type: :array}
+          }
+        }
+      ],
+      example: %{
+        "category" => "Fun",
+        "shortcode" => "aaaa",
+        "url" =>
+          "https://files.mastodon.social/custom_emojis/images/000/007/118/original/aaaa.png",
+        "static_url" =>
+          "https://files.mastodon.social/custom_emojis/images/000/007/118/static/aaaa.png",
+        "visible_in_picker" => true,
+        "tags" => ["Gif", "Fun"]
+      }
+    }
+  end
 end
diff --git a/lib/pleroma/web/api_spec/schemas/account.ex b/lib/pleroma/web/api_spec/schemas/account.ex
index d128feb30..d54e2158d 100644
--- a/lib/pleroma/web/api_spec/schemas/account.ex
+++ b/lib/pleroma/web/api_spec/schemas/account.ex
@@ -4,10 +4,10 @@
 
 defmodule Pleroma.Web.ApiSpec.Schemas.Account do
   alias OpenApiSpex.Schema
-  alias Pleroma.Web.ApiSpec.Schemas.AccountEmoji
   alias Pleroma.Web.ApiSpec.Schemas.AccountField
   alias Pleroma.Web.ApiSpec.Schemas.AccountRelationship
   alias Pleroma.Web.ApiSpec.Schemas.ActorType
+  alias Pleroma.Web.ApiSpec.Schemas.Emoji
   alias Pleroma.Web.ApiSpec.Schemas.FlakeID
   alias Pleroma.Web.ApiSpec.Schemas.VisibilityScope
 
@@ -24,7 +24,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Account do
       bot: %Schema{type: :boolean},
       created_at: %Schema{type: :string, format: "date-time"},
       display_name: %Schema{type: :string},
-      emojis: %Schema{type: :array, items: AccountEmoji},
+      emojis: %Schema{type: :array, items: Emoji},
       fields: %Schema{type: :array, items: AccountField},
       follow_requests_count: %Schema{type: :integer},
       followers_count: %Schema{type: :integer},
diff --git a/lib/pleroma/web/api_spec/schemas/account_create_request.ex b/lib/pleroma/web/api_spec/schemas/account_create_request.ex
deleted file mode 100644
index 49fa12159..000000000
--- a/lib/pleroma/web/api_spec/schemas/account_create_request.ex
+++ /dev/null
@@ -1,60 +0,0 @@
-# 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.AccountCreateRequest do
-  alias OpenApiSpex.Schema
-  require OpenApiSpex
-
-  OpenApiSpex.schema(%{
-    title: "AccountCreateRequest",
-    description: "POST body for creating an account",
-    type: :object,
-    properties: %{
-      reason: %Schema{
-        type: :string,
-        description:
-          "Text that will be reviewed by moderators if registrations require manual approval"
-      },
-      username: %Schema{type: :string, description: "The desired username for the account"},
-      email: %Schema{
-        type: :string,
-        description:
-          "The email address to be used for login. Required when `account_activation_required` is enabled.",
-        format: :email
-      },
-      password: %Schema{
-        type: :string,
-        description: "The password to be used for login",
-        format: :password
-      },
-      agreement: %Schema{
-        type: :boolean,
-        description:
-          "Whether the user agrees to the local rules, terms, and policies. These should be presented to the user in order to allow them to consent before setting this parameter to TRUE."
-      },
-      locale: %Schema{
-        type: :string,
-        description: "The language of the confirmation email that will be sent"
-      },
-      # Pleroma-specific properties:
-      fullname: %Schema{type: :string, description: "Full name"},
-      bio: %Schema{type: :string, description: "Bio", default: ""},
-      captcha_solution: %Schema{type: :string, description: "Provider-specific captcha solution"},
-      captcha_token: %Schema{type: :string, description: "Provider-specific captcha token"},
-      captcha_answer_data: %Schema{type: :string, description: "Provider-specific captcha data"},
-      token: %Schema{
-        type: :string,
-        description: "Invite token required when the registrations aren't public"
-      }
-    },
-    required: [:username, :password, :agreement],
-    example: %{
-      "username" => "cofe",
-      "email" => "cofe@example.com",
-      "password" => "secret",
-      "agreement" => "true",
-      "bio" => "☕️"
-    }
-  })
-end
diff --git a/lib/pleroma/web/api_spec/schemas/account_create_response.ex b/lib/pleroma/web/api_spec/schemas/account_create_response.ex
deleted file mode 100644
index 2237351a2..000000000
--- a/lib/pleroma/web/api_spec/schemas/account_create_response.ex
+++ /dev/null
@@ -1,27 +0,0 @@
-# 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.AccountCreateResponse do
-  alias OpenApiSpex.Schema
-
-  require OpenApiSpex
-
-  OpenApiSpex.schema(%{
-    title: "AccountCreateResponse",
-    description: "Response schema for an account",
-    type: :object,
-    properties: %{
-      token_type: %Schema{type: :string},
-      access_token: %Schema{type: :string},
-      scope: %Schema{type: :array, items: %Schema{type: :string}},
-      created_at: %Schema{type: :integer, format: :"date-time"}
-    },
-    example: %{
-      "access_token" => "i9hAVVzGld86Pl5JtLtizKoXVvtTlSCJvwaugCxvZzk",
-      "created_at" => 1_585_918_714,
-      "scope" => ["read", "write", "follow", "push"],
-      "token_type" => "Bearer"
-    }
-  })
-end
diff --git a/lib/pleroma/web/api_spec/schemas/account_field_attribute.ex b/lib/pleroma/web/api_spec/schemas/account_field_attribute.ex
deleted file mode 100644
index 89e483655..000000000
--- a/lib/pleroma/web/api_spec/schemas/account_field_attribute.ex
+++ /dev/null
@@ -1,24 +0,0 @@
-# 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: %{
-      "name" => "Website",
-      "value" => "https://pleroma.com"
-    }
-  })
-end
diff --git a/lib/pleroma/web/api_spec/schemas/account_follows_request.ex b/lib/pleroma/web/api_spec/schemas/account_follows_request.ex
deleted file mode 100644
index 19dce0cb2..000000000
--- a/lib/pleroma/web/api_spec/schemas/account_follows_request.ex
+++ /dev/null
@@ -1,18 +0,0 @@
-# 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.AccountFollowsRequest do
-  alias OpenApiSpex.Schema
-  require OpenApiSpex
-
-  OpenApiSpex.schema(%{
-    title: "AccountFollowsRequest",
-    description: "POST body for muting an account",
-    type: :object,
-    properties: %{
-      uri: %Schema{type: :string, format: :uri}
-    },
-    required: [:uri]
-  })
-end
diff --git a/lib/pleroma/web/api_spec/schemas/account_mute_request.ex b/lib/pleroma/web/api_spec/schemas/account_mute_request.ex
deleted file mode 100644
index a61f6d04c..000000000
--- a/lib/pleroma/web/api_spec/schemas/account_mute_request.ex
+++ /dev/null
@@ -1,24 +0,0 @@
-# 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.AccountMuteRequest do
-  alias OpenApiSpex.Schema
-  require OpenApiSpex
-
-  OpenApiSpex.schema(%{
-    title: "AccountMuteRequest",
-    description: "POST body for muting an account",
-    type: :object,
-    properties: %{
-      notifications: %Schema{
-        type: :boolean,
-        description: "Mute notifications in addition to statuses? Defaults to true.",
-        default: true
-      }
-    },
-    example: %{
-      "notifications" => true
-    }
-  })
-end
diff --git a/lib/pleroma/web/api_spec/schemas/account_relationships_response.ex b/lib/pleroma/web/api_spec/schemas/account_relationships_response.ex
deleted file mode 100644
index 960e14db1..000000000
--- a/lib/pleroma/web/api_spec/schemas/account_relationships_response.ex
+++ /dev/null
@@ -1,58 +0,0 @@
-# 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.AccountRelationshipsResponse do
-  require OpenApiSpex
-
-  OpenApiSpex.schema(%{
-    title: "AccountRelationshipsResponse",
-    description: "Response schema for account relationships",
-    type: :array,
-    items: Pleroma.Web.ApiSpec.Schemas.AccountRelationship,
-    example: [
-      %{
-        "id" => "1",
-        "following" => true,
-        "showing_reblogs" => true,
-        "followed_by" => true,
-        "blocking" => false,
-        "blocked_by" => true,
-        "muting" => false,
-        "muting_notifications" => false,
-        "requested" => false,
-        "domain_blocking" => false,
-        "subscribing" => false,
-        "endorsed" => true
-      },
-      %{
-        "id" => "2",
-        "following" => true,
-        "showing_reblogs" => true,
-        "followed_by" => true,
-        "blocking" => false,
-        "blocked_by" => true,
-        "muting" => true,
-        "muting_notifications" => false,
-        "requested" => true,
-        "domain_blocking" => false,
-        "subscribing" => false,
-        "endorsed" => false
-      },
-      %{
-        "id" => "3",
-        "following" => true,
-        "showing_reblogs" => true,
-        "followed_by" => true,
-        "blocking" => true,
-        "blocked_by" => false,
-        "muting" => true,
-        "muting_notifications" => false,
-        "requested" => false,
-        "domain_blocking" => true,
-        "subscribing" => true,
-        "endorsed" => false
-      }
-    ]
-  })
-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
deleted file mode 100644
index 35220c78a..000000000
--- a/lib/pleroma/web/api_spec/schemas/account_update_credentials_request.ex
+++ /dev/null
@@ -1,125 +0,0 @@
-# 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
-  alias Pleroma.Web.ApiSpec.Schemas.ActorType
-  alias Pleroma.Web.ApiSpec.Schemas.VisibilityScope
-  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, additionalProperties: %Schema{type: AccountAttributeField}}
-        ]
-      },
-      # 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: VisibilityScope,
-      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: ActorType
-    },
-    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
diff --git a/lib/pleroma/web/api_spec/schemas/accounts_response.ex b/lib/pleroma/web/api_spec/schemas/accounts_response.ex
deleted file mode 100644
index b714f59e7..000000000
--- a/lib/pleroma/web/api_spec/schemas/accounts_response.ex
+++ /dev/null
@@ -1,13 +0,0 @@
-# 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.AccountsResponse do
-  require OpenApiSpex
-
-  OpenApiSpex.schema(%{
-    title: "AccountsResponse",
-    type: :array,
-    items: Pleroma.Web.ApiSpec.Schemas.Account
-  })
-end
diff --git a/lib/pleroma/web/api_spec/schemas/list.ex b/lib/pleroma/web/api_spec/schemas/api_error.ex
similarity index 52%
rename from lib/pleroma/web/api_spec/schemas/list.ex
rename to lib/pleroma/web/api_spec/schemas/api_error.ex
index f85fac2b8..5815df94c 100644
--- a/lib/pleroma/web/api_spec/schemas/list.ex
+++ b/lib/pleroma/web/api_spec/schemas/api_error.ex
@@ -2,22 +2,18 @@
 # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
 # SPDX-License-Identifier: AGPL-3.0-only
 
-defmodule Pleroma.Web.ApiSpec.Schemas.List do
+defmodule Pleroma.Web.ApiSpec.Schemas.ApiError do
   alias OpenApiSpex.Schema
 
   require OpenApiSpex
 
   OpenApiSpex.schema(%{
-    title: "List",
-    description: "Response schema for a list",
+    title: "ApiError",
+    description: "Response schema for API error",
     type: :object,
-    properties: %{
-      id: %Schema{type: :string},
-      title: %Schema{type: :string}
-    },
+    properties: %{error: %Schema{type: :string}},
     example: %{
-      "id" => "123",
-      "title" => "my list"
+      "error" => "Something went wrong"
     }
   })
 end
diff --git a/lib/pleroma/web/api_spec/schemas/custom_emoji.ex b/lib/pleroma/web/api_spec/schemas/custom_emoji.ex
deleted file mode 100644
index 5531b2081..000000000
--- a/lib/pleroma/web/api_spec/schemas/custom_emoji.ex
+++ /dev/null
@@ -1,30 +0,0 @@
-# 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.CustomEmoji do
-  alias OpenApiSpex.Schema
-
-  require OpenApiSpex
-
-  OpenApiSpex.schema(%{
-    title: "CustomEmoji",
-    description: "Response schema for an CustomEmoji",
-    type: :object,
-    properties: %{
-      shortcode: %Schema{type: :string},
-      url: %Schema{type: :string},
-      static_url: %Schema{type: :string},
-      visible_in_picker: %Schema{type: :boolean},
-      category: %Schema{type: :string},
-      tags: %Schema{type: :array}
-    },
-    example: %{
-      "shortcode" => "aaaa",
-      "url" => "https://files.mastodon.social/custom_emojis/images/000/007/118/original/aaaa.png",
-      "static_url" =>
-        "https://files.mastodon.social/custom_emojis/images/000/007/118/static/aaaa.png",
-      "visible_in_picker" => true
-    }
-  })
-end
diff --git a/lib/pleroma/web/api_spec/schemas/account_emoji.ex b/lib/pleroma/web/api_spec/schemas/emoji.ex
similarity index 85%
rename from lib/pleroma/web/api_spec/schemas/account_emoji.ex
rename to lib/pleroma/web/api_spec/schemas/emoji.ex
index 6c1d4d95c..26f35e648 100644
--- a/lib/pleroma/web/api_spec/schemas/account_emoji.ex
+++ b/lib/pleroma/web/api_spec/schemas/emoji.ex
@@ -2,14 +2,14 @@
 # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
 # SPDX-License-Identifier: AGPL-3.0-only
 
-defmodule Pleroma.Web.ApiSpec.Schemas.AccountEmoji do
+defmodule Pleroma.Web.ApiSpec.Schemas.Emoji do
   alias OpenApiSpex.Schema
 
   require OpenApiSpex
 
   OpenApiSpex.schema(%{
-    title: "AccountEmoji",
-    description: "Response schema for account custom fields",
+    title: "Emoji",
+    description: "Response schema for an emoji",
     type: :object,
     properties: %{
       shortcode: %Schema{type: :string},
diff --git a/lib/pleroma/web/api_spec/schemas/lists_response.ex b/lib/pleroma/web/api_spec/schemas/lists_response.ex
deleted file mode 100644
index 132454579..000000000
--- a/lib/pleroma/web/api_spec/schemas/lists_response.ex
+++ /dev/null
@@ -1,16 +0,0 @@
-# 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.ListsResponse do
-  alias Pleroma.Web.ApiSpec.Schemas.List
-
-  require OpenApiSpex
-
-  OpenApiSpex.schema(%{
-    title: "ListsResponse",
-    description: "Response schema for lists",
-    type: :array,
-    items: List
-  })
-end
diff --git a/lib/pleroma/web/api_spec/schemas/poll.ex b/lib/pleroma/web/api_spec/schemas/poll.ex
index 5fc9e889f..0474b550b 100644
--- a/lib/pleroma/web/api_spec/schemas/poll.ex
+++ b/lib/pleroma/web/api_spec/schemas/poll.ex
@@ -4,7 +4,7 @@
 
 defmodule Pleroma.Web.ApiSpec.Schemas.Poll do
   alias OpenApiSpex.Schema
-  alias Pleroma.Web.ApiSpec.Schemas.AccountEmoji
+  alias Pleroma.Web.ApiSpec.Schemas.Emoji
   alias Pleroma.Web.ApiSpec.Schemas.FlakeID
 
   require OpenApiSpex
@@ -20,7 +20,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Poll do
       multiple: %Schema{type: :boolean},
       votes_count: %Schema{type: :integer},
       voted: %Schema{type: :boolean},
-      emojis: %Schema{type: :array, items: AccountEmoji},
+      emojis: %Schema{type: :array, items: Emoji},
       options: %Schema{
         type: :array,
         items: %Schema{
diff --git a/lib/pleroma/web/api_spec/schemas/status.ex b/lib/pleroma/web/api_spec/schemas/status.ex
index bf5f04691..aef0588d4 100644
--- a/lib/pleroma/web/api_spec/schemas/status.ex
+++ b/lib/pleroma/web/api_spec/schemas/status.ex
@@ -5,7 +5,7 @@
 defmodule Pleroma.Web.ApiSpec.Schemas.Status do
   alias OpenApiSpex.Schema
   alias Pleroma.Web.ApiSpec.Schemas.Account
-  alias Pleroma.Web.ApiSpec.Schemas.AccountEmoji
+  alias Pleroma.Web.ApiSpec.Schemas.Emoji
   alias Pleroma.Web.ApiSpec.Schemas.FlakeID
   alias Pleroma.Web.ApiSpec.Schemas.Poll
   alias Pleroma.Web.ApiSpec.Schemas.VisibilityScope
@@ -41,7 +41,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Status do
       },
       content: %Schema{type: :string, format: :html},
       created_at: %Schema{type: :string, format: "date-time"},
-      emojis: %Schema{type: :array, items: AccountEmoji},
+      emojis: %Schema{type: :array, items: Emoji},
       favourited: %Schema{type: :boolean},
       favourites_count: %Schema{type: :integer},
       id: FlakeID,
diff --git a/lib/pleroma/web/api_spec/schemas/statuses_response.ex b/lib/pleroma/web/api_spec/schemas/statuses_response.ex
deleted file mode 100644
index fb7c7e0aa..000000000
--- a/lib/pleroma/web/api_spec/schemas/statuses_response.ex
+++ /dev/null
@@ -1,13 +0,0 @@
-# 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.StatusesResponse do
-  require OpenApiSpex
-
-  OpenApiSpex.schema(%{
-    title: "StatusesResponse",
-    type: :array,
-    items: Pleroma.Web.ApiSpec.Schemas.Status
-  })
-end
diff --git a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
index b1513001b..37adeec5f 100644
--- a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
@@ -104,8 +104,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
         :fullname
       ])
       |> Map.put(:nickname, params.username)
-      |> Map.put(:fullname, params.fullname || params.username)
-      |> Map.put(:bio, params.bio || "")
+      |> Map.put(:fullname, Map.get(params, :fullname, params.username))
       |> Map.put(:confirm, params.password)
       |> Map.put(:trusted_app, app.trusted)
 
@@ -158,7 +157,6 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
 
     params =
       params
-      |> Map.from_struct()
       |> Enum.filter(fn {_, value} -> not is_nil(value) end)
       |> Enum.into(%{})
 
@@ -217,11 +215,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
       Enum.map(fields, fn {_, v} -> v end)
     else
       Enum.map(fields, fn
-        %Pleroma.Web.ApiSpec.Schemas.AccountAttributeField{} = field ->
-          %{"name" => field.name, "value" => field.value}
-
-        field ->
-          field
+        %{} = field -> %{"name" => field.name, "value" => field.value}
+        field -> field
       end)
     end
   end
diff --git a/test/web/api_spec/account_operation_test.exs b/test/web/api_spec/account_operation_test.exs
deleted file mode 100644
index 892ade71c..000000000
--- a/test/web/api_spec/account_operation_test.exs
+++ /dev/null
@@ -1,141 +0,0 @@
-# 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.AccountOperationTest do
-  use Pleroma.Web.ConnCase
-
-  alias Pleroma.Web.ApiSpec
-  alias Pleroma.Web.ApiSpec.Schemas.Account
-  alias Pleroma.Web.ApiSpec.Schemas.AccountCreateRequest
-  alias Pleroma.Web.ApiSpec.Schemas.AccountCreateResponse
-  alias Pleroma.Web.ApiSpec.Schemas.AccountRelationshipsResponse
-  alias Pleroma.Web.ApiSpec.Schemas.AccountUpdateCredentialsRequest
-
-  import OpenApiSpex.TestAssertions
-  import Pleroma.Factory
-
-  test "Account example matches schema" do
-    api_spec = ApiSpec.spec()
-    schema = Account.schema()
-    assert_schema(schema.example, "Account", api_spec)
-  end
-
-  test "AccountCreateRequest example matches schema" do
-    api_spec = ApiSpec.spec()
-    schema = AccountCreateRequest.schema()
-    assert_schema(schema.example, "AccountCreateRequest", api_spec)
-  end
-
-  test "AccountCreateResponse example matches schema" do
-    api_spec = ApiSpec.spec()
-    schema = AccountCreateResponse.schema()
-    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)
-
-    json =
-      conn
-      |> put_req_header("authorization", "Bearer " <> app_token.token)
-      |> put_req_header("content-type", "application/json")
-      |> post(
-        "/api/v1/accounts",
-        %{
-          username: "foo",
-          email: "bar@example.org",
-          password: "qwerty",
-          agreement: true
-        }
-      )
-      |> json_response(200)
-
-    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
-
-  test "AccountRelationshipsResponse example matches schema" do
-    api_spec = ApiSpec.spec()
-    schema = AccountRelationshipsResponse.schema()
-    assert_schema(schema.example, "AccountRelationshipsResponse", api_spec)
-  end
-
-  test "/api/v1/accounts/relationships produces AccountRelationshipsResponse", %{
-    conn: conn
-  } do
-    token = insert(:oauth_token, scopes: ["read", "write"])
-    other_user = insert(:user)
-    {:ok, _user} = Pleroma.User.follow(token.user, other_user)
-    api_spec = ApiSpec.spec()
-
-    assert [relationship] =
-             conn
-             |> put_req_header("authorization", "Bearer " <> token.token)
-             |> get("/api/v1/accounts/relationships?id=#{other_user.id}")
-             |> json_response(:ok)
-
-    assert_schema([relationship], "AccountRelationshipsResponse", api_spec)
-  end
-
-  test "/api/v1/accounts/:id produces Account", %{
-    conn: conn
-  } do
-    user = insert(:user)
-    api_spec = ApiSpec.spec()
-
-    assert resp =
-             conn
-             |> get("/api/v1/accounts/#{user.id}")
-             |> json_response(:ok)
-
-    assert_schema(resp, "Account", api_spec)
-  end
-
-  test "/api/v1/accounts/:id/statuses produces StatusesResponse", %{
-    conn: conn
-  } do
-    user = insert(:user)
-    Pleroma.Web.CommonAPI.post(user, %{"status" => "foobar"})
-
-    api_spec = ApiSpec.spec()
-
-    assert resp =
-             conn
-             |> get("/api/v1/accounts/#{user.id}/statuses")
-             |> json_response(:ok)
-
-    assert_schema(resp, "StatusesResponse", api_spec)
-  end
-end
diff --git a/test/web/mastodon_api/controllers/custom_emoji_controller_test.exs b/test/web/mastodon_api/controllers/custom_emoji_controller_test.exs
index 4222556a4..ab0027f90 100644
--- a/test/web/mastodon_api/controllers/custom_emoji_controller_test.exs
+++ b/test/web/mastodon_api/controllers/custom_emoji_controller_test.exs
@@ -4,8 +4,6 @@
 
 defmodule Pleroma.Web.MastodonAPI.CustomEmojiControllerTest do
   use Pleroma.Web.ConnCase, async: true
-  alias Pleroma.Web.ApiSpec
-  import OpenApiSpex.TestAssertions
 
   test "with tags", %{conn: conn} do
     assert resp =
@@ -21,6 +19,5 @@ defmodule Pleroma.Web.MastodonAPI.CustomEmojiControllerTest do
     assert Map.has_key?(emoji, "category")
     assert Map.has_key?(emoji, "url")
     assert Map.has_key?(emoji, "visible_in_picker")
-    assert_schema(emoji, "CustomEmoji", ApiSpec.spec())
   end
 end

From 5ff20793e739daa962cdc1623c01dc6ec1ff8a61 Mon Sep 17 00:00:00 2001
From: fence <fence@desu-mail.moe>
Date: Tue, 28 Apr 2020 01:29:31 +0200
Subject: [PATCH 40/40] formating

---
 lib/pleroma/web/mongooseim/mongoose_im_controller.ex | 1 -
 test/web/mongooseim/mongoose_im_controller_test.exs  | 9 +++++++--
 2 files changed, 7 insertions(+), 3 deletions(-)

diff --git a/lib/pleroma/web/mongooseim/mongoose_im_controller.ex b/lib/pleroma/web/mongooseim/mongoose_im_controller.ex
index 7123153c5..1ed6ee521 100644
--- a/lib/pleroma/web/mongooseim/mongoose_im_controller.ex
+++ b/lib/pleroma/web/mongooseim/mongoose_im_controller.ex
@@ -44,4 +44,3 @@ defmodule Pleroma.Web.MongooseIM.MongooseIMController do
     end
   end
 end
-
diff --git a/test/web/mongooseim/mongoose_im_controller_test.exs b/test/web/mongooseim/mongoose_im_controller_test.exs
index d17f8dbb4..1ac2f2c27 100644
--- a/test/web/mongooseim/mongoose_im_controller_test.exs
+++ b/test/web/mongooseim/mongoose_im_controller_test.exs
@@ -42,7 +42,13 @@ defmodule Pleroma.Web.MongooseIMController do
 
   test "/check_password", %{conn: conn} do
     user = insert(:user, password_hash: Comeonin.Pbkdf2.hashpwsalt("cool"))
-    _deactivated_user = insert(:user, nickname: "konata", deactivated: true, password_hash: Comeonin.Pbkdf2.hashpwsalt("cool"))
+
+    _deactivated_user =
+      insert(:user,
+        nickname: "konata",
+        deactivated: true,
+        password_hash: Comeonin.Pbkdf2.hashpwsalt("cool")
+      )
 
     res =
       conn
@@ -65,7 +71,6 @@ defmodule Pleroma.Web.MongooseIMController do
 
     assert res == false
 
-
     res =
       conn
       |> get(mongoose_im_path(conn, :check_password), user: "nobody", pass: "cool")