Add OpenAPI spec for AdminAPI.OAuthAppContoller
authorEgor Kislitsyn <egor@kislitsyn.com>
Mon, 25 May 2020 18:02:22 +0000 (22:02 +0400)
committerEgor Kislitsyn <egor@kislitsyn.com>
Mon, 25 May 2020 18:04:06 +0000 (22:04 +0400)
lib/pleroma/web/admin_api/controllers/admin_api_controller.ex
lib/pleroma/web/admin_api/controllers/oauth_app_controller.ex [new file with mode: 0644]
lib/pleroma/web/api_spec/operations/admin/oauth_app_operation.ex [new file with mode: 0644]
lib/pleroma/web/oauth/app.ex
lib/pleroma/web/router.ex
test/web/admin_api/controllers/admin_api_controller_test.exs
test/web/admin_api/controllers/oauth_app_controller_test.exs [new file with mode: 0644]

index 6b1d64a2eb3152f3ae0f916283f6af2fc7cf2162..4f10bd9478bdfabb63eba90c06b9997061afdd4b 100644 (file)
@@ -32,8 +32,6 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
   alias Pleroma.Web.CommonAPI
   alias Pleroma.Web.Endpoint
   alias Pleroma.Web.MastodonAPI
-  alias Pleroma.Web.MastodonAPI.AppView
-  alias Pleroma.Web.OAuth.App
   alias Pleroma.Web.Router
 
   require Logger
@@ -122,10 +120,6 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
            :config_update,
            :resend_confirmation_email,
            :confirm_email,
-           :oauth_app_create,
-           :oauth_app_list,
-           :oauth_app_update,
-           :oauth_app_delete,
            :reload_emoji
          ]
   )
@@ -995,83 +989,6 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
     conn |> json("")
   end
 
-  def oauth_app_create(conn, params) do
-    params =
-      if params["name"] do
-        Map.put(params, "client_name", params["name"])
-      else
-        params
-      end
-
-    result =
-      case App.create(params) do
-        {:ok, app} ->
-          AppView.render("show.json", %{app: app, admin: true})
-
-        {:error, changeset} ->
-          App.errors(changeset)
-      end
-
-    json(conn, result)
-  end
-
-  def oauth_app_update(conn, params) do
-    params =
-      if params["name"] do
-        Map.put(params, "client_name", params["name"])
-      else
-        params
-      end
-
-    with {:ok, app} <- App.update(params) do
-      json(conn, AppView.render("show.json", %{app: app, admin: true}))
-    else
-      {:error, changeset} ->
-        json(conn, App.errors(changeset))
-
-      nil ->
-        json_response(conn, :bad_request, "")
-    end
-  end
-
-  def oauth_app_list(conn, params) do
-    {page, page_size} = page_params(params)
-
-    search_params = %{
-      client_name: params["name"],
-      client_id: params["client_id"],
-      page: page,
-      page_size: page_size
-    }
-
-    search_params =
-      if Map.has_key?(params, "trusted") do
-        Map.put(search_params, :trusted, params["trusted"])
-      else
-        search_params
-      end
-
-    with {:ok, apps, count} <- App.search(search_params) do
-      json(
-        conn,
-        AppView.render("index.json",
-          apps: apps,
-          count: count,
-          page_size: page_size,
-          admin: true
-        )
-      )
-    end
-  end
-
-  def oauth_app_delete(conn, params) do
-    with {:ok, _app} <- App.destroy(params["id"]) do
-      json_response(conn, :no_content, "")
-    else
-      _ -> json_response(conn, :bad_request, "")
-    end
-  end
-
   def stats(conn, _) do
     count = Stats.get_status_visibility_count()
 
diff --git a/lib/pleroma/web/admin_api/controllers/oauth_app_controller.ex b/lib/pleroma/web/admin_api/controllers/oauth_app_controller.ex
new file mode 100644 (file)
index 0000000..04e629f
--- /dev/null
@@ -0,0 +1,87 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.AdminAPI.OAuthAppController do
+  use Pleroma.Web, :controller
+
+  import Pleroma.Web.ControllerHelper, only: [json_response: 3]
+
+  alias Pleroma.Plugs.OAuthScopesPlug
+  alias Pleroma.Web.OAuth.App
+
+  require Logger
+
+  plug(Pleroma.Web.ApiSpec.CastAndValidate)
+  plug(:put_view, Pleroma.Web.MastodonAPI.AppView)
+
+  plug(
+    OAuthScopesPlug,
+    %{scopes: ["write"], admin: true}
+    when action in [:create, :index, :update, :delete]
+  )
+
+  action_fallback(Pleroma.Web.AdminAPI.FallbackController)
+
+  defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.Admin.OAuthAppOperation
+
+  def index(conn, params) do
+    search_params =
+      params
+      |> Map.take([:client_id, :page, :page_size, :trusted])
+      |> Map.put(:client_name, params[:name])
+
+    with {:ok, apps, count} <- App.search(search_params) do
+      render(conn, "index.json",
+        apps: apps,
+        count: count,
+        page_size: params.page_size,
+        admin: true
+      )
+    end
+  end
+
+  def create(%{body_params: params} = conn, _) do
+    params =
+      if params[:name] do
+        Map.put(params, :client_name, params[:name])
+      else
+        params
+      end
+
+    case App.create(params) do
+      {:ok, app} ->
+        render(conn, "show.json", app: app, admin: true)
+
+      {:error, changeset} ->
+        json(conn, App.errors(changeset))
+    end
+  end
+
+  def update(%{body_params: params} = conn, %{id: id}) do
+    params =
+      if params[:name] do
+        Map.put(params, :client_name, params.name)
+      else
+        params
+      end
+
+    with {:ok, app} <- App.update(id, params) do
+      render(conn, "show.json", app: app, admin: true)
+    else
+      {:error, changeset} ->
+        json(conn, App.errors(changeset))
+
+      nil ->
+        json_response(conn, :bad_request, "")
+    end
+  end
+
+  def delete(conn, params) do
+    with {:ok, _app} <- App.destroy(params.id) do
+      json_response(conn, :no_content, "")
+    else
+      _ -> json_response(conn, :bad_request, "")
+    end
+  end
+end
diff --git a/lib/pleroma/web/api_spec/operations/admin/oauth_app_operation.ex b/lib/pleroma/web/api_spec/operations/admin/oauth_app_operation.ex
new file mode 100644 (file)
index 0000000..fbc9f80
--- /dev/null
@@ -0,0 +1,215 @@
+# 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.Admin.OAuthAppOperation do
+  alias OpenApiSpex.Operation
+  alias OpenApiSpex.Schema
+  alias Pleroma.Web.ApiSpec.Schemas.ApiError
+
+  import Pleroma.Web.ApiSpec.Helpers
+
+  def open_api_operation(action) do
+    operation = String.to_existing_atom("#{action}_operation")
+    apply(__MODULE__, operation, [])
+  end
+
+  def index_operation do
+    %Operation{
+      summary: "List OAuth apps",
+      tags: ["Admin", "oAuth Apps"],
+      operationId: "AdminAPI.OAuthAppController.index",
+      security: [%{"oAuth" => ["write"]}],
+      parameters: [
+        Operation.parameter(:name, :query, %Schema{type: :string}, "App name"),
+        Operation.parameter(:client_id, :query, %Schema{type: :string}, "Client ID"),
+        Operation.parameter(:page, :query, %Schema{type: :integer, default: 1}, "Page"),
+        Operation.parameter(
+          :trusted,
+          :query,
+          %Schema{type: :boolean, default: false},
+          "Trusted apps"
+        ),
+        Operation.parameter(
+          :page_size,
+          :query,
+          %Schema{type: :integer, default: 50},
+          "Number of apps to return"
+        )
+      ],
+      responses: %{
+        200 =>
+          Operation.response("List of apps", "application/json", %Schema{
+            type: :object,
+            properties: %{
+              apps: %Schema{type: :array, items: oauth_app()},
+              count: %Schema{type: :integer},
+              page_size: %Schema{type: :integer}
+            },
+            example: %{
+              "apps" => [
+                %{
+                  "id" => 1,
+                  "name" => "App name",
+                  "client_id" => "yHoDSiWYp5mPV6AfsaVOWjdOyt5PhWRiafi6MRd1lSk",
+                  "client_secret" => "nLmis486Vqrv2o65eM9mLQx_m_4gH-Q6PcDpGIMl6FY",
+                  "redirect_uri" => "https://example.com/oauth-callback",
+                  "website" => "https://example.com",
+                  "trusted" => true
+                }
+              ],
+              "count" => 1,
+              "page_size" => 50
+            }
+          })
+      }
+    }
+  end
+
+  def create_operation do
+    %Operation{
+      tags: ["Admin", "oAuth Apps"],
+      summary: "Create OAuth App",
+      operationId: "AdminAPI.OAuthAppController.create",
+      requestBody: request_body("Parameters", create_request()),
+      security: [%{"oAuth" => ["write"]}],
+      responses: %{
+        200 => Operation.response("App", "application/json", oauth_app()),
+        400 => Operation.response("Bad Request", "application/json", ApiError)
+      }
+    }
+  end
+
+  def update_operation do
+    %Operation{
+      tags: ["Admin", "oAuth Apps"],
+      summary: "Update OAuth App",
+      operationId: "AdminAPI.OAuthAppController.update",
+      parameters: [id_param()],
+      security: [%{"oAuth" => ["write"]}],
+      requestBody: request_body("Parameters", update_request()),
+      responses: %{
+        200 => Operation.response("App", "application/json", oauth_app()),
+        400 =>
+          Operation.response("Bad Request", "application/json", %Schema{
+            oneOf: [ApiError, %Schema{type: :string}]
+          })
+      }
+    }
+  end
+
+  def delete_operation do
+    %Operation{
+      tags: ["Admin", "oAuth Apps"],
+      summary: "Delete OAuth App",
+      operationId: "AdminAPI.OAuthAppController.delete",
+      parameters: [id_param()],
+      security: [%{"oAuth" => ["write"]}],
+      responses: %{
+        204 => no_content_response(),
+        400 => no_content_response()
+      }
+    }
+  end
+
+  defp create_request do
+    %Schema{
+      title: "oAuthAppCreateRequest",
+      type: :object,
+      required: [:name, :redirect_uris],
+      properties: %{
+        name: %Schema{type: :string, description: "Application Name"},
+        scopes: %Schema{type: :array, items: %Schema{type: :string}, description: "oAuth scopes"},
+        redirect_uris: %Schema{
+          type: :string,
+          description:
+            "Where the user should be redirected after authorization. To display the authorization code to the user instead of redirecting to a web page, use `urn:ietf:wg:oauth:2.0:oob` in this parameter."
+        },
+        website: %Schema{
+          type: :string,
+          nullable: true,
+          description: "A URL to the homepage of the app"
+        },
+        trusted: %Schema{
+          type: :boolean,
+          nullable: true,
+          default: false,
+          description: "Is the app trusted?"
+        }
+      },
+      example: %{
+        "name" => "My App",
+        "redirect_uris" => "https://myapp.com/auth/callback",
+        "website" => "https://myapp.com/",
+        "scopes" => ["read", "write"],
+        "trusted" => true
+      }
+    }
+  end
+
+  defp update_request do
+    %Schema{
+      title: "oAuthAppUpdateRequest",
+      type: :object,
+      properties: %{
+        name: %Schema{type: :string, description: "Application Name"},
+        scopes: %Schema{type: :array, items: %Schema{type: :string}, description: "oAuth scopes"},
+        redirect_uris: %Schema{
+          type: :string,
+          description:
+            "Where the user should be redirected after authorization. To display the authorization code to the user instead of redirecting to a web page, use `urn:ietf:wg:oauth:2.0:oob` in this parameter."
+        },
+        website: %Schema{
+          type: :string,
+          nullable: true,
+          description: "A URL to the homepage of the app"
+        },
+        trusted: %Schema{
+          type: :boolean,
+          nullable: true,
+          default: false,
+          description: "Is the app trusted?"
+        }
+      },
+      example: %{
+        "name" => "My App",
+        "redirect_uris" => "https://myapp.com/auth/callback",
+        "website" => "https://myapp.com/",
+        "scopes" => ["read", "write"],
+        "trusted" => true
+      }
+    }
+  end
+
+  defp oauth_app do
+    %Schema{
+      title: "oAuthApp",
+      type: :object,
+      properties: %{
+        id: %Schema{type: :integer},
+        name: %Schema{type: :string},
+        client_id: %Schema{type: :string},
+        client_secret: %Schema{type: :string},
+        redirect_uri: %Schema{type: :string},
+        website: %Schema{type: :string, nullable: true},
+        trusted: %Schema{type: :boolean}
+      },
+      example: %{
+        "id" => 123,
+        "name" => "My App",
+        "client_id" => "TWhM-tNSuncnqN7DBJmoyeLnk6K3iJJ71KKXxgL1hPM",
+        "client_secret" => "ZEaFUFmF0umgBX1qKJDjaU99Q31lDkOU8NutzTOoliw",
+        "redirect_uri" => "https://myapp.com/oauth-callback",
+        "website" => "https://myapp.com/",
+        "trusted" => false
+      }
+    }
+  end
+
+  def id_param do
+    Operation.parameter(:id, :path, :integer, "App ID",
+      example: 1337,
+      required: true
+    )
+  end
+end
index 6a6d5f2e2a40a1dce242af3c3e811e0ac23f8136..df99472e19092d7175c245134ae5d7e6aa8acbfb 100644 (file)
@@ -25,12 +25,12 @@ defmodule Pleroma.Web.OAuth.App do
     timestamps()
   end
 
-  @spec changeset(App.t(), map()) :: Ecto.Changeset.t()
+  @spec changeset(t(), map()) :: Ecto.Changeset.t()
   def changeset(struct, params) do
     cast(struct, params, [:client_name, :redirect_uris, :scopes, :website, :trusted])
   end
 
-  @spec register_changeset(App.t(), map()) :: Ecto.Changeset.t()
+  @spec register_changeset(t(), map()) :: Ecto.Changeset.t()
   def register_changeset(struct, params \\ %{}) do
     changeset =
       struct
@@ -52,18 +52,19 @@ defmodule Pleroma.Web.OAuth.App do
     end
   end
 
-  @spec create(map()) :: {:ok, App.t()} | {:error, Ecto.Changeset.t()}
+  @spec create(map()) :: {:ok, t()} | {:error, Ecto.Changeset.t()}
   def create(params) do
-    with changeset <- __MODULE__.register_changeset(%__MODULE__{}, params) do
-      Repo.insert(changeset)
-    end
+    %__MODULE__{}
+    |> register_changeset(params)
+    |> Repo.insert()
   end
 
-  @spec update(map()) :: {:ok, App.t()} | {:error, Ecto.Changeset.t()}
-  def update(params) do
-    with %__MODULE__{} = app <- Repo.get(__MODULE__, params["id"]),
-         changeset <- changeset(app, params) do
-      Repo.update(changeset)
+  @spec update(pos_integer(), map()) :: {:ok, t()} | {:error, Ecto.Changeset.t()}
+  def update(id, params) do
+    with %__MODULE__{} = app <- Repo.get(__MODULE__, id) do
+      app
+      |> changeset(params)
+      |> Repo.update()
     end
   end
 
@@ -71,7 +72,7 @@ defmodule Pleroma.Web.OAuth.App do
   Gets app by attrs or create new  with attrs.
   And updates the scopes if need.
   """
-  @spec get_or_make(map(), list(String.t())) :: {:ok, App.t()} | {:error, Ecto.Changeset.t()}
+  @spec get_or_make(map(), list(String.t())) :: {:ok, t()} | {:error, Ecto.Changeset.t()}
   def get_or_make(attrs, scopes) do
     with %__MODULE__{} = app <- Repo.get_by(__MODULE__, attrs) do
       update_scopes(app, scopes)
@@ -92,7 +93,7 @@ defmodule Pleroma.Web.OAuth.App do
     |> Repo.update()
   end
 
-  @spec search(map()) :: {:ok, [App.t()], non_neg_integer()}
+  @spec search(map()) :: {:ok, [t()], non_neg_integer()}
   def search(params) do
     query = from(a in __MODULE__)
 
@@ -128,7 +129,7 @@ defmodule Pleroma.Web.OAuth.App do
     {:ok, Repo.all(query), count}
   end
 
-  @spec destroy(pos_integer()) :: {:ok, App.t()} | {:error, Ecto.Changeset.t()}
+  @spec destroy(pos_integer()) :: {:ok, t()} | {:error, Ecto.Changeset.t()}
   def destroy(id) do
     with %__MODULE__{} = app <- Repo.get(__MODULE__, id) do
       Repo.delete(app)
index e493a41534bb0f8b3a0a11e0eb6ec7714e4fd803..46f03cdfdf120ce8e1bebbf3b4777849f01a7074 100644 (file)
@@ -205,10 +205,10 @@ defmodule Pleroma.Web.Router do
     post("/reload_emoji", AdminAPIController, :reload_emoji)
     get("/stats", AdminAPIController, :stats)
 
-    get("/oauth_app", AdminAPIController, :oauth_app_list)
-    post("/oauth_app", AdminAPIController, :oauth_app_create)
-    patch("/oauth_app/:id", AdminAPIController, :oauth_app_update)
-    delete("/oauth_app/:id", AdminAPIController, :oauth_app_delete)
+    get("/oauth_app", OAuthAppController, :index)
+    post("/oauth_app", OAuthAppController, :create)
+    patch("/oauth_app/:id", OAuthAppController, :update)
+    delete("/oauth_app/:id", OAuthAppController, :delete)
   end
 
   scope "/api/pleroma/emoji", Pleroma.Web.PleromaAPI do
index 321840a8c31ff34222ca991ca3459ba6073a4859..f704cdd3ad5f82377d33c05d5c6ddca23b5bb20c 100644 (file)
@@ -3522,191 +3522,6 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
                response["status_visibility"]
     end
   end
-
-  describe "POST /api/pleroma/admin/oauth_app" do
-    test "errors", %{conn: conn} do
-      response = conn |> post("/api/pleroma/admin/oauth_app", %{}) |> json_response(200)
-
-      assert response == %{"name" => "can't be blank", "redirect_uris" => "can't be blank"}
-    end
-
-    test "success", %{conn: conn} do
-      base_url = Web.base_url()
-      app_name = "Trusted app"
-
-      response =
-        conn
-        |> post("/api/pleroma/admin/oauth_app", %{
-          name: app_name,
-          redirect_uris: base_url
-        })
-        |> json_response(200)
-
-      assert %{
-               "client_id" => _,
-               "client_secret" => _,
-               "name" => ^app_name,
-               "redirect_uri" => ^base_url,
-               "trusted" => false
-             } = response
-    end
-
-    test "with trusted", %{conn: conn} do
-      base_url = Web.base_url()
-      app_name = "Trusted app"
-
-      response =
-        conn
-        |> post("/api/pleroma/admin/oauth_app", %{
-          name: app_name,
-          redirect_uris: base_url,
-          trusted: true
-        })
-        |> json_response(200)
-
-      assert %{
-               "client_id" => _,
-               "client_secret" => _,
-               "name" => ^app_name,
-               "redirect_uri" => ^base_url,
-               "trusted" => true
-             } = response
-    end
-  end
-
-  describe "GET /api/pleroma/admin/oauth_app" do
-    setup do
-      app = insert(:oauth_app)
-      {:ok, app: app}
-    end
-
-    test "list", %{conn: conn} do
-      response =
-        conn
-        |> get("/api/pleroma/admin/oauth_app")
-        |> json_response(200)
-
-      assert %{"apps" => apps, "count" => count, "page_size" => _} = response
-
-      assert length(apps) == count
-    end
-
-    test "with page size", %{conn: conn} do
-      insert(:oauth_app)
-      page_size = 1
-
-      response =
-        conn
-        |> get("/api/pleroma/admin/oauth_app", %{page_size: to_string(page_size)})
-        |> json_response(200)
-
-      assert %{"apps" => apps, "count" => _, "page_size" => ^page_size} = response
-
-      assert length(apps) == page_size
-    end
-
-    test "search by client name", %{conn: conn, app: app} do
-      response =
-        conn
-        |> get("/api/pleroma/admin/oauth_app", %{name: app.client_name})
-        |> json_response(200)
-
-      assert %{"apps" => [returned], "count" => _, "page_size" => _} = response
-
-      assert returned["client_id"] == app.client_id
-      assert returned["name"] == app.client_name
-    end
-
-    test "search by client id", %{conn: conn, app: app} do
-      response =
-        conn
-        |> get("/api/pleroma/admin/oauth_app", %{client_id: app.client_id})
-        |> json_response(200)
-
-      assert %{"apps" => [returned], "count" => _, "page_size" => _} = response
-
-      assert returned["client_id"] == app.client_id
-      assert returned["name"] == app.client_name
-    end
-
-    test "only trusted", %{conn: conn} do
-      app = insert(:oauth_app, trusted: true)
-
-      response =
-        conn
-        |> get("/api/pleroma/admin/oauth_app", %{trusted: true})
-        |> json_response(200)
-
-      assert %{"apps" => [returned], "count" => _, "page_size" => _} = response
-
-      assert returned["client_id"] == app.client_id
-      assert returned["name"] == app.client_name
-    end
-  end
-
-  describe "DELETE /api/pleroma/admin/oauth_app/:id" do
-    test "with id", %{conn: conn} do
-      app = insert(:oauth_app)
-
-      response =
-        conn
-        |> delete("/api/pleroma/admin/oauth_app/" <> to_string(app.id))
-        |> json_response(:no_content)
-
-      assert response == ""
-    end
-
-    test "with non existance id", %{conn: conn} do
-      response =
-        conn
-        |> delete("/api/pleroma/admin/oauth_app/0")
-        |> json_response(:bad_request)
-
-      assert response == ""
-    end
-  end
-
-  describe "PATCH /api/pleroma/admin/oauth_app/:id" do
-    test "with id", %{conn: conn} do
-      app = insert(:oauth_app)
-
-      name = "another name"
-      url = "https://example.com"
-      scopes = ["admin"]
-      id = app.id
-      website = "http://website.com"
-
-      response =
-        conn
-        |> patch("/api/pleroma/admin/oauth_app/" <> to_string(app.id), %{
-          name: name,
-          trusted: true,
-          redirect_uris: url,
-          scopes: scopes,
-          website: website
-        })
-        |> json_response(200)
-
-      assert %{
-               "client_id" => _,
-               "client_secret" => _,
-               "id" => ^id,
-               "name" => ^name,
-               "redirect_uri" => ^url,
-               "trusted" => true,
-               "website" => ^website
-             } = response
-    end
-
-    test "without id", %{conn: conn} do
-      response =
-        conn
-        |> patch("/api/pleroma/admin/oauth_app/0")
-        |> json_response(:bad_request)
-
-      assert response == ""
-    end
-  end
 end
 
 # Needed for testing
diff --git a/test/web/admin_api/controllers/oauth_app_controller_test.exs b/test/web/admin_api/controllers/oauth_app_controller_test.exs
new file mode 100644 (file)
index 0000000..ed7c417
--- /dev/null
@@ -0,0 +1,220 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.AdminAPI.OAuthAppControllerTest do
+  use Pleroma.Web.ConnCase, async: true
+  use Oban.Testing, repo: Pleroma.Repo
+
+  import Pleroma.Factory
+
+  alias Pleroma.Config
+  alias Pleroma.Web
+
+  setup do
+    admin = insert(:user, is_admin: true)
+    token = insert(:oauth_admin_token, user: admin)
+
+    conn =
+      build_conn()
+      |> assign(:user, admin)
+      |> assign(:token, token)
+
+    {:ok, %{admin: admin, token: token, conn: conn}}
+  end
+
+  describe "POST /api/pleroma/admin/oauth_app" do
+    test "errors", %{conn: conn} do
+      response =
+        conn
+        |> put_req_header("content-type", "application/json")
+        |> post("/api/pleroma/admin/oauth_app", %{})
+        |> json_response_and_validate_schema(400)
+
+      assert %{
+               "error" => "Missing field: name. Missing field: redirect_uris."
+             } = response
+    end
+
+    test "success", %{conn: conn} do
+      base_url = Web.base_url()
+      app_name = "Trusted app"
+
+      response =
+        conn
+        |> put_req_header("content-type", "application/json")
+        |> post("/api/pleroma/admin/oauth_app", %{
+          name: app_name,
+          redirect_uris: base_url
+        })
+        |> json_response_and_validate_schema(200)
+
+      assert %{
+               "client_id" => _,
+               "client_secret" => _,
+               "name" => ^app_name,
+               "redirect_uri" => ^base_url,
+               "trusted" => false
+             } = response
+    end
+
+    test "with trusted", %{conn: conn} do
+      base_url = Web.base_url()
+      app_name = "Trusted app"
+
+      response =
+        conn
+        |> put_req_header("content-type", "application/json")
+        |> post("/api/pleroma/admin/oauth_app", %{
+          name: app_name,
+          redirect_uris: base_url,
+          trusted: true
+        })
+        |> json_response_and_validate_schema(200)
+
+      assert %{
+               "client_id" => _,
+               "client_secret" => _,
+               "name" => ^app_name,
+               "redirect_uri" => ^base_url,
+               "trusted" => true
+             } = response
+    end
+  end
+
+  describe "GET /api/pleroma/admin/oauth_app" do
+    setup do
+      app = insert(:oauth_app)
+      {:ok, app: app}
+    end
+
+    test "list", %{conn: conn} do
+      response =
+        conn
+        |> get("/api/pleroma/admin/oauth_app")
+        |> json_response_and_validate_schema(200)
+
+      assert %{"apps" => apps, "count" => count, "page_size" => _} = response
+
+      assert length(apps) == count
+    end
+
+    test "with page size", %{conn: conn} do
+      insert(:oauth_app)
+      page_size = 1
+
+      response =
+        conn
+        |> get("/api/pleroma/admin/oauth_app?page_size=#{page_size}")
+        |> json_response_and_validate_schema(200)
+
+      assert %{"apps" => apps, "count" => _, "page_size" => ^page_size} = response
+
+      assert length(apps) == page_size
+    end
+
+    test "search by client name", %{conn: conn, app: app} do
+      response =
+        conn
+        |> get("/api/pleroma/admin/oauth_app?name=#{app.client_name}")
+        |> json_response_and_validate_schema(200)
+
+      assert %{"apps" => [returned], "count" => _, "page_size" => _} = response
+
+      assert returned["client_id"] == app.client_id
+      assert returned["name"] == app.client_name
+    end
+
+    test "search by client id", %{conn: conn, app: app} do
+      response =
+        conn
+        |> get("/api/pleroma/admin/oauth_app?client_id=#{app.client_id}")
+        |> json_response_and_validate_schema(200)
+
+      assert %{"apps" => [returned], "count" => _, "page_size" => _} = response
+
+      assert returned["client_id"] == app.client_id
+      assert returned["name"] == app.client_name
+    end
+
+    test "only trusted", %{conn: conn} do
+      app = insert(:oauth_app, trusted: true)
+
+      response =
+        conn
+        |> get("/api/pleroma/admin/oauth_app?trusted=true")
+        |> json_response_and_validate_schema(200)
+
+      assert %{"apps" => [returned], "count" => _, "page_size" => _} = response
+
+      assert returned["client_id"] == app.client_id
+      assert returned["name"] == app.client_name
+    end
+  end
+
+  describe "DELETE /api/pleroma/admin/oauth_app/:id" do
+    test "with id", %{conn: conn} do
+      app = insert(:oauth_app)
+
+      response =
+        conn
+        |> delete("/api/pleroma/admin/oauth_app/" <> to_string(app.id))
+        |> json_response_and_validate_schema(:no_content)
+
+      assert response == ""
+    end
+
+    test "with non existance id", %{conn: conn} do
+      response =
+        conn
+        |> delete("/api/pleroma/admin/oauth_app/0")
+        |> json_response_and_validate_schema(:bad_request)
+
+      assert response == ""
+    end
+  end
+
+  describe "PATCH /api/pleroma/admin/oauth_app/:id" do
+    test "with id", %{conn: conn} do
+      app = insert(:oauth_app)
+
+      name = "another name"
+      url = "https://example.com"
+      scopes = ["admin"]
+      id = app.id
+      website = "http://website.com"
+
+      response =
+        conn
+        |> put_req_header("content-type", "application/json")
+        |> patch("/api/pleroma/admin/oauth_app/#{id}", %{
+          name: name,
+          trusted: true,
+          redirect_uris: url,
+          scopes: scopes,
+          website: website
+        })
+        |> json_response_and_validate_schema(200)
+
+      assert %{
+               "client_id" => _,
+               "client_secret" => _,
+               "id" => ^id,
+               "name" => ^name,
+               "redirect_uri" => ^url,
+               "trusted" => true,
+               "website" => ^website
+             } = response
+    end
+
+    test "without id", %{conn: conn} do
+      response =
+        conn
+        |> put_req_header("content-type", "application/json")
+        |> patch("/api/pleroma/admin/oauth_app/0")
+        |> json_response_and_validate_schema(:bad_request)
+
+      assert response == ""
+    end
+  end
+end