Add OpenAPI spec for ListController
authorEgor Kislitsyn <egor@kislitsyn.com>
Tue, 5 May 2020 13:05:34 +0000 (17:05 +0400)
committerEgor Kislitsyn <egor@kislitsyn.com>
Tue, 5 May 2020 13:05:34 +0000 (17:05 +0400)
lib/pleroma/web/api_spec/operations/account_operation.ex
lib/pleroma/web/api_spec/operations/list_operation.ex [new file with mode: 0644]
lib/pleroma/web/api_spec/schemas/list.ex [new file with mode: 0644]
lib/pleroma/web/mastodon_api/controllers/list_controller.ex
test/support/conn_case.ex
test/web/mastodon_api/controllers/list_controller_test.exs

index fe9548b1b8afd0218975a78b45f09654f77f1d1b..470fc0215f63a3a0fce3634a9419068e32eeed61 100644 (file)
@@ -11,6 +11,7 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
   alias Pleroma.Web.ApiSpec.Schemas.ActorType
   alias Pleroma.Web.ApiSpec.Schemas.ApiError
   alias Pleroma.Web.ApiSpec.Schemas.BooleanLike
+  alias Pleroma.Web.ApiSpec.Schemas.List
   alias Pleroma.Web.ApiSpec.Schemas.Status
   alias Pleroma.Web.ApiSpec.Schemas.VisibilityScope
 
@@ -646,28 +647,12 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
     }
   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(),
+      items: List,
       example: [
         %{"id" => "123", "title" => "my list"},
         %{"id" => "1337", "title" => "anotehr list"}
diff --git a/lib/pleroma/web/api_spec/operations/list_operation.ex b/lib/pleroma/web/api_spec/operations/list_operation.ex
new file mode 100644 (file)
index 0000000..bb903a3
--- /dev/null
@@ -0,0 +1,189 @@
+# 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.ListOperation do
+  alias OpenApiSpex.Operation
+  alias OpenApiSpex.Schema
+  alias Pleroma.Web.ApiSpec.Schemas.Account
+  alias Pleroma.Web.ApiSpec.Schemas.ApiError
+  alias Pleroma.Web.ApiSpec.Schemas.FlakeID
+  alias Pleroma.Web.ApiSpec.Schemas.List
+
+  import Pleroma.Web.ApiSpec.Helpers
+
+  def open_api_operation(action) do
+    operation = String.to_existing_atom("#{action}_operation")
+    apply(__MODULE__, operation, [])
+  end
+
+  def index_operation do
+    %Operation{
+      tags: ["Lists"],
+      summary: "Show user's lists",
+      description: "Fetch all lists that the user owns",
+      security: [%{"oAuth" => ["read:lists"]}],
+      operationId: "ListController.index",
+      responses: %{
+        200 => Operation.response("Array of List", "application/json", array_of_lists())
+      }
+    }
+  end
+
+  def create_operation do
+    %Operation{
+      tags: ["Lists"],
+      summary: "Show a single list",
+      description: "Fetch the list with the given ID. Used for verifying the title of a list.",
+      operationId: "ListController.create",
+      requestBody: create_update_request(),
+      security: [%{"oAuth" => ["write:lists"]}],
+      responses: %{
+        200 => Operation.response("List", "application/json", List),
+        400 => Operation.response("Error", "application/json", ApiError),
+        404 => Operation.response("Error", "application/json", ApiError)
+      }
+    }
+  end
+
+  def show_operation do
+    %Operation{
+      tags: ["Lists"],
+      summary: "Show a single list",
+      description: "Fetch the list with the given ID. Used for verifying the title of a list.",
+      operationId: "ListController.show",
+      parameters: [id_param()],
+      security: [%{"oAuth" => ["read:lists"]}],
+      responses: %{
+        200 => Operation.response("List", "application/json", List),
+        404 => Operation.response("Error", "application/json", ApiError)
+      }
+    }
+  end
+
+  def update_operation do
+    %Operation{
+      tags: ["Lists"],
+      summary: "Update a list",
+      description: "Change the title of a list",
+      operationId: "ListController.update",
+      parameters: [id_param()],
+      requestBody: create_update_request(),
+      security: [%{"oAuth" => ["write:lists"]}],
+      responses: %{
+        200 => Operation.response("List", "application/json", List),
+        422 => Operation.response("Error", "application/json", ApiError)
+      }
+    }
+  end
+
+  def delete_operation do
+    %Operation{
+      tags: ["Lists"],
+      summary: "Delete a list",
+      operationId: "ListController.delete",
+      parameters: [id_param()],
+      security: [%{"oAuth" => ["write:lists"]}],
+      responses: %{
+        200 => Operation.response("Empty object", "application/json", %Schema{type: :object})
+      }
+    }
+  end
+
+  def list_accounts_operation do
+    %Operation{
+      tags: ["Lists"],
+      summary: "View accounts in list",
+      operationId: "ListController.list_accounts",
+      parameters: [id_param()],
+      security: [%{"oAuth" => ["read:lists"]}],
+      responses: %{
+        200 =>
+          Operation.response("Array of Account", "application/json", %Schema{
+            type: :array,
+            items: Account
+          })
+      }
+    }
+  end
+
+  def add_to_list_operation do
+    %Operation{
+      tags: ["Lists"],
+      summary: "Add accounts to list",
+      description:
+        "Add accounts to the given list. Note that the user must be following these accounts.",
+      operationId: "ListController.add_to_list",
+      parameters: [id_param()],
+      requestBody: add_remove_accounts_request(),
+      security: [%{"oAuth" => ["write:lists"]}],
+      responses: %{
+        200 => Operation.response("Empty object", "application/json", %Schema{type: :object})
+      }
+    }
+  end
+
+  def remove_from_list_operation do
+    %Operation{
+      tags: ["Lists"],
+      summary: "Remove accounts from list",
+      operationId: "ListController.remove_from_list",
+      parameters: [id_param()],
+      requestBody: add_remove_accounts_request(),
+      security: [%{"oAuth" => ["write:lists"]}],
+      responses: %{
+        200 => Operation.response("Empty object", "application/json", %Schema{type: :object})
+      }
+    }
+  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" => "another list"}
+      ]
+    }
+  end
+
+  defp id_param do
+    Operation.parameter(:id, :path, :string, "List ID",
+      example: "123",
+      required: true
+    )
+  end
+
+  defp create_update_request do
+    request_body(
+      "Parameters",
+      %Schema{
+        description: "POST body for creating or updating a List",
+        type: :object,
+        properties: %{
+          title: %Schema{type: :string, description: "List title"}
+        },
+        required: [:title]
+      },
+      required: true
+    )
+  end
+
+  defp add_remove_accounts_request do
+    request_body(
+      "Parameters",
+      %Schema{
+        description: "POST body for adding/removing accounts to/from a List",
+        type: :object,
+        properties: %{
+          account_ids: %Schema{type: :array, description: "Array of account IDs", items: FlakeID}
+        },
+        required: [:account_ids]
+      },
+      required: true
+    )
+  end
+end
diff --git a/lib/pleroma/web/api_spec/schemas/list.ex b/lib/pleroma/web/api_spec/schemas/list.ex
new file mode 100644 (file)
index 0000000..78aa073
--- /dev/null
@@ -0,0 +1,23 @@
+# 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: "Represents a list of some users that the authenticated user follows",
+    type: :object,
+    properties: %{
+      id: %Schema{type: :string, description: "The internal database ID of the list"},
+      title: %Schema{type: :string, description: "The user-defined title of the list"}
+    },
+    example: %{
+      "id" => "12249",
+      "title" => "Friends"
+    }
+  })
+end
index bfe856025af0303882afbce96460a84da69fc1d1..acdc76fd217af2fbf9678d5b1dafe5e52822d928 100644 (file)
@@ -9,20 +9,17 @@ defmodule Pleroma.Web.MastodonAPI.ListController do
   alias Pleroma.User
   alias Pleroma.Web.MastodonAPI.AccountView
 
-  plug(:list_by_id_and_user when action not in [:index, :create])
-
   @oauth_read_actions [:index, :show, :list_accounts]
 
+  plug(Pleroma.Web.ApiSpec.CastAndValidate)
+  plug(:list_by_id_and_user when action not in [:index, :create])
   plug(OAuthScopesPlug, %{scopes: ["read:lists"]} when action in @oauth_read_actions)
-
-  plug(
-    OAuthScopesPlug,
-    %{scopes: ["write:lists"]}
-    when action not in @oauth_read_actions
-  )
+  plug(OAuthScopesPlug, %{scopes: ["write:lists"]} when action not in @oauth_read_actions)
 
   action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
 
+  defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.ListOperation
+
   # GET /api/v1/lists
   def index(%{assigns: %{user: user}} = conn, opts) do
     lists = Pleroma.List.for_user(user, opts)
@@ -30,7 +27,7 @@ defmodule Pleroma.Web.MastodonAPI.ListController do
   end
 
   # POST /api/v1/lists
-  def create(%{assigns: %{user: user}} = conn, %{"title" => title}) do
+  def create(%{assigns: %{user: user}, body_params: %{title: title}} = conn, _) do
     with {:ok, %Pleroma.List{} = list} <- Pleroma.List.create(title, user) do
       render(conn, "show.json", list: list)
     end
@@ -42,7 +39,7 @@ defmodule Pleroma.Web.MastodonAPI.ListController do
   end
 
   # PUT /api/v1/lists/:id
-  def update(%{assigns: %{list: list}} = conn, %{"title" => title}) do
+  def update(%{assigns: %{list: list}, body_params: %{title: title}} = conn, _) do
     with {:ok, list} <- Pleroma.List.rename(list, title) do
       render(conn, "show.json", list: list)
     end
@@ -65,7 +62,7 @@ defmodule Pleroma.Web.MastodonAPI.ListController do
   end
 
   # POST /api/v1/lists/:id/accounts
-  def add_to_list(%{assigns: %{list: list}} = conn, %{"account_ids" => account_ids}) do
+  def add_to_list(%{assigns: %{list: list}, body_params: %{account_ids: account_ids}} = conn, _) do
     Enum.each(account_ids, fn account_id ->
       with %User{} = followed <- User.get_cached_by_id(account_id) do
         Pleroma.List.follow(list, followed)
@@ -76,7 +73,10 @@ defmodule Pleroma.Web.MastodonAPI.ListController do
   end
 
   # DELETE /api/v1/lists/:id/accounts
-  def remove_from_list(%{assigns: %{list: list}} = conn, %{"account_ids" => account_ids}) do
+  def remove_from_list(
+        %{assigns: %{list: list}, body_params: %{account_ids: account_ids}} = conn,
+        _
+      ) do
     Enum.each(account_ids, fn account_id ->
       with %User{} = followed <- User.get_cached_by_id(account_id) do
         Pleroma.List.unfollow(list, followed)
@@ -86,7 +86,7 @@ defmodule Pleroma.Web.MastodonAPI.ListController do
     json(conn, %{})
   end
 
-  defp list_by_id_and_user(%{assigns: %{user: user}, params: %{"id" => id}} = conn, _) do
+  defp list_by_id_and_user(%{assigns: %{user: user}, params: %{id: id}} = conn, _) do
     case Pleroma.List.get(id, user) do
       %Pleroma.List{} = list -> assign(conn, :list, list)
       nil -> conn |> render_error(:not_found, "List not found") |> halt()
index fa30a0c41f109f2151e4285d8d26a19267ebe477..91c03b1a88843fd4d3919251b3bb4025a4846960 100644 (file)
@@ -74,7 +74,7 @@ defmodule Pleroma.Web.ConnCase do
         status = Plug.Conn.Status.code(status)
 
         unless lookup[op_id].responses[status] do
-          err = "Response schema not found for #{conn.status} #{conn.method} #{conn.request_path}"
+          err = "Response schema not found for #{status} #{conn.method} #{conn.request_path}"
           flunk(err)
         end
 
index c9c4cbb49f943f4ff8438d853aea77f1b7dfc1ef..57a9ef4a44ddf9bf97814e88a7355d2cefe19c0e 100644 (file)
@@ -12,37 +12,44 @@ defmodule Pleroma.Web.MastodonAPI.ListControllerTest do
   test "creating a list" do
     %{conn: conn} = oauth_access(["write:lists"])
 
-    conn = post(conn, "/api/v1/lists", %{"title" => "cuties"})
-
-    assert %{"title" => title} = json_response(conn, 200)
-    assert title == "cuties"
+    assert %{"title" => "cuties"} =
+             conn
+             |> put_req_header("content-type", "application/json")
+             |> post("/api/v1/lists", %{"title" => "cuties"})
+             |> json_response_and_validate_schema(:ok)
   end
 
   test "renders error for invalid params" do
     %{conn: conn} = oauth_access(["write:lists"])
 
-    conn = post(conn, "/api/v1/lists", %{"title" => nil})
+    conn =
+      conn
+      |> put_req_header("content-type", "application/json")
+      |> post("/api/v1/lists", %{"title" => nil})
 
-    assert %{"error" => "can't be blank"} == json_response(conn, :unprocessable_entity)
+    assert %{"error" => "title - null value where string expected."} =
+             json_response_and_validate_schema(conn, 400)
   end
 
   test "listing a user's lists" do
     %{conn: conn} = oauth_access(["read:lists", "write:lists"])
 
     conn
+    |> put_req_header("content-type", "application/json")
     |> post("/api/v1/lists", %{"title" => "cuties"})
-    |> json_response(:ok)
+    |> json_response_and_validate_schema(:ok)
 
     conn
+    |> put_req_header("content-type", "application/json")
     |> post("/api/v1/lists", %{"title" => "cofe"})
-    |> json_response(:ok)
+    |> json_response_and_validate_schema(:ok)
 
     conn = get(conn, "/api/v1/lists")
 
     assert [
              %{"id" => _, "title" => "cofe"},
              %{"id" => _, "title" => "cuties"}
-           ] = json_response(conn, :ok)
+           ] = json_response_and_validate_schema(conn, :ok)
   end
 
   test "adding users to a list" do
@@ -50,9 +57,12 @@ defmodule Pleroma.Web.MastodonAPI.ListControllerTest do
     other_user = insert(:user)
     {:ok, list} = Pleroma.List.create("name", user)
 
-    conn = post(conn, "/api/v1/lists/#{list.id}/accounts", %{"account_ids" => [other_user.id]})
+    assert %{} ==
+             conn
+             |> put_req_header("content-type", "application/json")
+             |> post("/api/v1/lists/#{list.id}/accounts", %{"account_ids" => [other_user.id]})
+             |> json_response_and_validate_schema(:ok)
 
-    assert %{} == json_response(conn, 200)
     %Pleroma.List{following: following} = Pleroma.List.get(list.id, user)
     assert following == [other_user.follower_address]
   end
@@ -65,9 +75,12 @@ defmodule Pleroma.Web.MastodonAPI.ListControllerTest do
     {:ok, list} = Pleroma.List.follow(list, other_user)
     {:ok, list} = Pleroma.List.follow(list, third_user)
 
-    conn = delete(conn, "/api/v1/lists/#{list.id}/accounts", %{"account_ids" => [other_user.id]})
+    assert %{} ==
+             conn
+             |> put_req_header("content-type", "application/json")
+             |> delete("/api/v1/lists/#{list.id}/accounts", %{"account_ids" => [other_user.id]})
+             |> json_response_and_validate_schema(:ok)
 
-    assert %{} == json_response(conn, 200)
     %Pleroma.List{following: following} = Pleroma.List.get(list.id, user)
     assert following == [third_user.follower_address]
   end
@@ -83,7 +96,7 @@ defmodule Pleroma.Web.MastodonAPI.ListControllerTest do
       |> assign(:user, user)
       |> get("/api/v1/lists/#{list.id}/accounts", %{"account_ids" => [other_user.id]})
 
-    assert [%{"id" => id}] = json_response(conn, 200)
+    assert [%{"id" => id}] = json_response_and_validate_schema(conn, 200)
     assert id == to_string(other_user.id)
   end
 
@@ -96,7 +109,7 @@ defmodule Pleroma.Web.MastodonAPI.ListControllerTest do
       |> assign(:user, user)
       |> get("/api/v1/lists/#{list.id}")
 
-    assert %{"id" => id} = json_response(conn, 200)
+    assert %{"id" => id} = json_response_and_validate_schema(conn, 200)
     assert id == to_string(list.id)
   end
 
@@ -105,17 +118,18 @@ defmodule Pleroma.Web.MastodonAPI.ListControllerTest do
 
     conn = get(conn, "/api/v1/lists/666")
 
-    assert %{"error" => "List not found"} = json_response(conn, :not_found)
+    assert %{"error" => "List not found"} = json_response_and_validate_schema(conn, :not_found)
   end
 
   test "renaming a list" do
     %{user: user, conn: conn} = oauth_access(["write:lists"])
     {:ok, list} = Pleroma.List.create("name", user)
 
-    conn = put(conn, "/api/v1/lists/#{list.id}", %{"title" => "newname"})
-
-    assert %{"title" => name} = json_response(conn, 200)
-    assert name == "newname"
+    assert %{"title" => "newname"} =
+             conn
+             |> put_req_header("content-type", "application/json")
+             |> put("/api/v1/lists/#{list.id}", %{"title" => "newname"})
+             |> json_response_and_validate_schema(:ok)
   end
 
   test "validates title when renaming a list" do
@@ -125,9 +139,11 @@ defmodule Pleroma.Web.MastodonAPI.ListControllerTest do
     conn =
       conn
       |> assign(:user, user)
+      |> put_req_header("content-type", "application/json")
       |> put("/api/v1/lists/#{list.id}", %{"title" => "  "})
 
-    assert %{"error" => "can't be blank"} == json_response(conn, :unprocessable_entity)
+    assert %{"error" => "can't be blank"} ==
+             json_response_and_validate_schema(conn, :unprocessable_entity)
   end
 
   test "deleting a list" do
@@ -136,7 +152,7 @@ defmodule Pleroma.Web.MastodonAPI.ListControllerTest do
 
     conn = delete(conn, "/api/v1/lists/#{list.id}")
 
-    assert %{} = json_response(conn, 200)
+    assert %{} = json_response_and_validate_schema(conn, 200)
     assert is_nil(Repo.get(Pleroma.List, list.id))
   end
 end