From 3b1b631c2aedc8e359c296b11237fa4f6edd31e5 Mon Sep 17 00:00:00 2001
From: Egor Kislitsyn <egor@kislitsyn.com>
Date: Mon, 26 Aug 2019 18:59:57 +0700
Subject: [PATCH 1/5] Add validation in Pleroma.List.create/2

---
 lib/pleroma/list.ex | 18 +++++++++++-------
 test/list_test.exs  |  7 +++++++
 2 files changed, 18 insertions(+), 7 deletions(-)

diff --git a/lib/pleroma/list.ex b/lib/pleroma/list.ex
index 1d320206e..c572380c2 100644
--- a/lib/pleroma/list.ex
+++ b/lib/pleroma/list.ex
@@ -109,15 +109,19 @@ def rename(%Pleroma.List{} = list, title) do
   end
 
   def create(title, %User{} = creator) do
-    list = %Pleroma.List{user_id: creator.id, title: title}
+    changeset = title_changeset(%Pleroma.List{user_id: creator.id}, %{title: title})
 
-    Repo.transaction(fn ->
-      list = Repo.insert!(list)
+    if changeset.valid? do
+      Repo.transaction(fn ->
+        list = Repo.insert!(changeset)
 
-      list
-      |> change(ap_id: "#{creator.ap_id}/lists/#{list.id}")
-      |> Repo.update!()
-    end)
+        list
+        |> change(ap_id: "#{creator.ap_id}/lists/#{list.id}")
+        |> Repo.update!()
+      end)
+    else
+      {:error, changeset}
+    end
   end
 
   def follow(%Pleroma.List{following: following} = list, %User{} = followed) do
diff --git a/test/list_test.exs b/test/list_test.exs
index f39033d02..8efba75ea 100644
--- a/test/list_test.exs
+++ b/test/list_test.exs
@@ -15,6 +15,13 @@ test "creating a list" do
     assert title == "title"
   end
 
+  test "validates title" do
+    user = insert(:user)
+
+    assert {:error, changeset} = Pleroma.List.create("", user)
+    assert changeset.errors == [title: {"can't be blank", [validation: :required]}]
+  end
+
   test "getting a list not belonging to the user" do
     user = insert(:user)
     other_user = insert(:user)

From 4d82bc8b0b5a0b8b584b43330f902f8dc9637d3d Mon Sep 17 00:00:00 2001
From: Egor Kislitsyn <egor@kislitsyn.com>
Date: Mon, 26 Aug 2019 19:16:40 +0700
Subject: [PATCH 2/5] Extract MastodonAPI.MastodonAPIController.errors/2 to
 MastodonAPI.FallbackController

---
 .../controllers/fallback_controller.ex        | 34 +++++++++++++++++++
 .../mastodon_api/mastodon_api_controller.ex   | 31 +----------------
 .../mastodon_api/subscription_controller.ex   |  4 +--
 3 files changed, 36 insertions(+), 33 deletions(-)
 create mode 100644 lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex

diff --git a/lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex b/lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex
new file mode 100644
index 000000000..41243d5e7
--- /dev/null
+++ b/lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex
@@ -0,0 +1,34 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.MastodonAPI.FallbackController do
+  use Pleroma.Web, :controller
+
+  def call(conn, {:error, %Ecto.Changeset{} = changeset}) do
+    error_message =
+      changeset
+      |> Ecto.Changeset.traverse_errors(fn {message, _opt} -> message end)
+      |> Enum.map_join(", ", fn {_k, v} -> v end)
+
+    conn
+    |> put_status(:unprocessable_entity)
+    |> json(%{error: error_message})
+  end
+
+  def call(conn, {:error, :not_found}) do
+    render_error(conn, :not_found, "Record not found")
+  end
+
+  def call(conn, {:error, error_message}) do
+    conn
+    |> put_status(:bad_request)
+    |> json(%{error: error_message})
+  end
+
+  def call(conn, _) do
+    conn
+    |> put_status(:internal_server_error)
+    |> json(dgettext("errors", "Something went wrong"))
+  end
+end
diff --git a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex
index 53cf95fbb..e51b2d89c 100644
--- a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex
+++ b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex
@@ -83,7 +83,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
 
   @local_mastodon_name "Mastodon-Local"
 
-  action_fallback(:errors)
+  action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
 
   def create_app(conn, params) do
     scopes = Scopes.fetch_scopes(params, ["read"])
@@ -1587,35 +1587,6 @@ def delete_filter(%{assigns: %{user: user}} = conn, %{"id" => filter_id}) do
     json(conn, %{})
   end
 
-  # fallback action
-  #
-  def errors(conn, {:error, %Changeset{} = changeset}) do
-    error_message =
-      changeset
-      |> Changeset.traverse_errors(fn {message, _opt} -> message end)
-      |> Enum.map_join(", ", fn {_k, v} -> v end)
-
-    conn
-    |> put_status(:unprocessable_entity)
-    |> json(%{error: error_message})
-  end
-
-  def errors(conn, {:error, :not_found}) do
-    render_error(conn, :not_found, "Record not found")
-  end
-
-  def errors(conn, {:error, error_message}) do
-    conn
-    |> put_status(:bad_request)
-    |> json(%{error: error_message})
-  end
-
-  def errors(conn, _) do
-    conn
-    |> put_status(:internal_server_error)
-    |> json(dgettext("errors", "Something went wrong"))
-  end
-
   def suggestions(%{assigns: %{user: user}} = conn, _) do
     suggestions = Config.get(:suggestions)
 
diff --git a/lib/pleroma/web/mastodon_api/subscription_controller.ex b/lib/pleroma/web/mastodon_api/subscription_controller.ex
index 255ee2f18..e2b17aab1 100644
--- a/lib/pleroma/web/mastodon_api/subscription_controller.ex
+++ b/lib/pleroma/web/mastodon_api/subscription_controller.ex
@@ -64,8 +64,6 @@ def errors(conn, {:error, :not_found}) do
   end
 
   def errors(conn, _) do
-    conn
-    |> put_status(:internal_server_error)
-    |> json(dgettext("errors", "Something went wrong"))
+    Pleroma.Web.MastodonAPI.FallbackController.call(conn, nil)
   end
 end

From 30510ade0e2f813413c5599245adc4dae8c7ffd8 Mon Sep 17 00:00:00 2001
From: Egor Kislitsyn <egor@kislitsyn.com>
Date: Mon, 26 Aug 2019 19:37:54 +0700
Subject: [PATCH 3/5] Extract MastodonAPIController's list actions into
 MastodonAPI.ListController; Add more tests

---
 .../controllers/list_controller.ex            |  84 +++++++++
 .../mastodon_api/mastodon_api_controller.ex   |  76 --------
 .../web/mastodon_api/views/list_view.ex       |   6 +-
 lib/pleroma/web/router.ex                     |  16 +-
 .../controllers/list_controller_test.exs      | 166 ++++++++++++++++++
 .../mastodon_api_controller_test.exs          | 101 +----------
 .../{ => views}/list_view_test.exs            |  14 +-
 7 files changed, 274 insertions(+), 189 deletions(-)
 create mode 100644 lib/pleroma/web/mastodon_api/controllers/list_controller.ex
 create mode 100644 test/web/mastodon_api/controllers/list_controller_test.exs
 rename test/web/mastodon_api/{ => views}/list_view_test.exs (56%)

diff --git a/lib/pleroma/web/mastodon_api/controllers/list_controller.ex b/lib/pleroma/web/mastodon_api/controllers/list_controller.ex
new file mode 100644
index 000000000..2873deda8
--- /dev/null
+++ b/lib/pleroma/web/mastodon_api/controllers/list_controller.ex
@@ -0,0 +1,84 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.MastodonAPI.ListController do
+  use Pleroma.Web, :controller
+
+  alias Pleroma.User
+  alias Pleroma.Web.MastodonAPI.AccountView
+
+  plug(:list_by_id_and_user when action not in [:index, :create])
+
+  action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
+
+  # GET /api/v1/lists
+  def index(%{assigns: %{user: user}} = conn, opts) do
+    lists = Pleroma.List.for_user(user, opts)
+    render(conn, "index.json", lists: lists)
+  end
+
+  # POST /api/v1/lists
+  def create(%{assigns: %{user: user}} = conn, %{"title" => title}) do
+    with {:ok, %Pleroma.List{} = list} <- Pleroma.List.create(title, user) do
+      render(conn, "show.json", list: list)
+    end
+  end
+
+  # GET /api/v1/lists/:id
+  def show(%{assigns: %{list: list}} = conn, _) do
+    render(conn, "show.json", list: list)
+  end
+
+  # PUT /api/v1/lists/:id
+  def update(%{assigns: %{list: list}} = conn, %{"title" => title}) do
+    with {:ok, list} <- Pleroma.List.rename(list, title) do
+      render(conn, "show.json", list: list)
+    end
+  end
+
+  # DELETE /api/v1/lists/:id
+  def delete(%{assigns: %{list: list}} = conn, _) do
+    with {:ok, _list} <- Pleroma.List.delete(list) do
+      json(conn, %{})
+    end
+  end
+
+  # GET /api/v1/lists/:id/accounts
+  def list_accounts(%{assigns: %{user: user, list: list}} = conn, _) do
+    with {:ok, users} <- Pleroma.List.get_following(list) do
+      conn
+      |> put_view(AccountView)
+      |> render("accounts.json", for: user, users: users, as: :user)
+    end
+  end
+
+  # POST /api/v1/lists/:id/accounts
+  def add_to_list(%{assigns: %{list: list}} = conn, %{"account_ids" => account_ids}) do
+    Enum.each(account_ids, fn account_id ->
+      with %User{} = followed <- User.get_cached_by_id(account_id) do
+        Pleroma.List.follow(list, followed)
+      end
+    end)
+
+    json(conn, %{})
+  end
+
+  # DELETE /api/v1/lists/:id/accounts
+  def remove_from_list(%{assigns: %{list: list}} = conn, %{"account_ids" => account_ids}) do
+    Enum.each(account_ids, fn account_id ->
+      with %User{} = followed <- User.get_cached_by_id(account_id) do
+        Pleroma.List.unfollow(list, followed)
+      end
+    end)
+
+    json(conn, %{})
+  end
+
+  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()
+    end
+  end
+end
diff --git a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex
index e51b2d89c..31b0aaca0 100644
--- a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex
+++ b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex
@@ -1205,88 +1205,12 @@ def bookmarks(%{assigns: %{user: user}} = conn, params) do
     |> render("index.json", %{activities: activities, for: user, as: :activity})
   end
 
-  def get_lists(%{assigns: %{user: user}} = conn, opts) do
-    lists = Pleroma.List.for_user(user, opts)
-    res = ListView.render("lists.json", lists: lists)
-    json(conn, res)
-  end
-
-  def get_list(%{assigns: %{user: user}} = conn, %{"id" => id}) do
-    with %Pleroma.List{} = list <- Pleroma.List.get(id, user) do
-      res = ListView.render("list.json", list: list)
-      json(conn, res)
-    else
-      _e -> render_error(conn, :not_found, "Record not found")
-    end
-  end
-
   def account_lists(%{assigns: %{user: user}} = conn, %{"id" => account_id}) do
     lists = Pleroma.List.get_lists_account_belongs(user, account_id)
     res = ListView.render("lists.json", lists: lists)
     json(conn, res)
   end
 
-  def delete_list(%{assigns: %{user: user}} = conn, %{"id" => id}) do
-    with %Pleroma.List{} = list <- Pleroma.List.get(id, user),
-         {:ok, _list} <- Pleroma.List.delete(list) do
-      json(conn, %{})
-    else
-      _e ->
-        json(conn, dgettext("errors", "error"))
-    end
-  end
-
-  def create_list(%{assigns: %{user: user}} = conn, %{"title" => title}) do
-    with {:ok, %Pleroma.List{} = list} <- Pleroma.List.create(title, user) do
-      res = ListView.render("list.json", list: list)
-      json(conn, res)
-    end
-  end
-
-  def add_to_list(%{assigns: %{user: user}} = conn, %{"id" => id, "account_ids" => accounts}) do
-    accounts
-    |> Enum.each(fn account_id ->
-      with %Pleroma.List{} = list <- Pleroma.List.get(id, user),
-           %User{} = followed <- User.get_cached_by_id(account_id) do
-        Pleroma.List.follow(list, followed)
-      end
-    end)
-
-    json(conn, %{})
-  end
-
-  def remove_from_list(%{assigns: %{user: user}} = conn, %{"id" => id, "account_ids" => accounts}) do
-    accounts
-    |> Enum.each(fn account_id ->
-      with %Pleroma.List{} = list <- Pleroma.List.get(id, user),
-           %User{} = followed <- User.get_cached_by_id(account_id) do
-        Pleroma.List.unfollow(list, followed)
-      end
-    end)
-
-    json(conn, %{})
-  end
-
-  def list_accounts(%{assigns: %{user: user}} = conn, %{"id" => id}) do
-    with %Pleroma.List{} = list <- Pleroma.List.get(id, user),
-         {:ok, users} = Pleroma.List.get_following(list) do
-      conn
-      |> put_view(AccountView)
-      |> render("accounts.json", %{for: user, users: users, as: :user})
-    end
-  end
-
-  def rename_list(%{assigns: %{user: user}} = conn, %{"id" => id, "title" => title}) do
-    with %Pleroma.List{} = list <- Pleroma.List.get(id, user),
-         {:ok, list} <- Pleroma.List.rename(list, title) do
-      res = ListView.render("list.json", list: list)
-      json(conn, res)
-    else
-      _e ->
-        json(conn, dgettext("errors", "error"))
-    end
-  end
-
   def list_timeline(%{assigns: %{user: user}} = conn, %{"list_id" => id} = params) do
     with %Pleroma.List{title: _title, following: following} <- Pleroma.List.get(id, user) do
       params =
diff --git a/lib/pleroma/web/mastodon_api/views/list_view.ex b/lib/pleroma/web/mastodon_api/views/list_view.ex
index 0f86e2512..bfda6f5b3 100644
--- a/lib/pleroma/web/mastodon_api/views/list_view.ex
+++ b/lib/pleroma/web/mastodon_api/views/list_view.ex
@@ -6,11 +6,11 @@ defmodule Pleroma.Web.MastodonAPI.ListView do
   use Pleroma.Web, :view
   alias Pleroma.Web.MastodonAPI.ListView
 
-  def render("lists.json", %{lists: lists} = opts) do
-    render_many(lists, ListView, "list.json", opts)
+  def render("index.json", %{lists: lists} = opts) do
+    render_many(lists, ListView, "show.json", opts)
   end
 
-  def render("list.json", %{list: list}) do
+  def render("show.json", %{list: list}) do
     %{
       id: to_string(list.id),
       title: list.title
diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex
index 1ad33630c..969dc66fd 100644
--- a/lib/pleroma/web/router.ex
+++ b/lib/pleroma/web/router.ex
@@ -312,9 +312,9 @@ defmodule Pleroma.Web.Router do
       get("/scheduled_statuses", MastodonAPIController, :scheduled_statuses)
       get("/scheduled_statuses/:id", MastodonAPIController, :show_scheduled_status)
 
-      get("/lists", MastodonAPIController, :get_lists)
-      get("/lists/:id", MastodonAPIController, :get_list)
-      get("/lists/:id/accounts", MastodonAPIController, :list_accounts)
+      get("/lists", ListController, :index)
+      get("/lists/:id", ListController, :show)
+      get("/lists/:id/accounts", ListController, :list_accounts)
 
       get("/domain_blocks", MastodonAPIController, :domain_blocks)
 
@@ -355,12 +355,12 @@ defmodule Pleroma.Web.Router do
       post("/media", MastodonAPIController, :upload)
       put("/media/:id", MastodonAPIController, :update_media)
 
-      delete("/lists/:id", MastodonAPIController, :delete_list)
-      post("/lists", MastodonAPIController, :create_list)
-      put("/lists/:id", MastodonAPIController, :rename_list)
+      delete("/lists/:id", ListController, :delete)
+      post("/lists", ListController, :create)
+      put("/lists/:id", ListController, :update)
 
-      post("/lists/:id/accounts", MastodonAPIController, :add_to_list)
-      delete("/lists/:id/accounts", MastodonAPIController, :remove_from_list)
+      post("/lists/:id/accounts", ListController, :add_to_list)
+      delete("/lists/:id/accounts", ListController, :remove_from_list)
 
       post("/filters", MastodonAPIController, :create_filter)
       get("/filters/:id", MastodonAPIController, :get_filter)
diff --git a/test/web/mastodon_api/controllers/list_controller_test.exs b/test/web/mastodon_api/controllers/list_controller_test.exs
new file mode 100644
index 000000000..093506309
--- /dev/null
+++ b/test/web/mastodon_api/controllers/list_controller_test.exs
@@ -0,0 +1,166 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.MastodonAPI.ListControllerTest do
+  use Pleroma.Web.ConnCase
+
+  alias Pleroma.Repo
+
+  import Pleroma.Factory
+
+  test "creating a list", %{conn: conn} do
+    user = insert(:user)
+
+    conn =
+      conn
+      |> assign(:user, user)
+      |> post("/api/v1/lists", %{"title" => "cuties"})
+
+    assert %{"title" => title} = json_response(conn, 200)
+    assert title == "cuties"
+  end
+
+  test "renders error for invalid params", %{conn: conn} do
+    user = insert(:user)
+
+    conn =
+      conn
+      |> assign(:user, user)
+      |> post("/api/v1/lists", %{"title" => nil})
+
+    assert %{"error" => "can't be blank"} == json_response(conn, :unprocessable_entity)
+  end
+
+  test "listing a user's lists", %{conn: conn} do
+    user = insert(:user)
+
+    conn
+    |> assign(:user, user)
+    |> post("/api/v1/lists", %{"title" => "cuties"})
+
+    conn
+    |> assign(:user, user)
+    |> post("/api/v1/lists", %{"title" => "cofe"})
+
+    conn =
+      conn
+      |> assign(:user, user)
+      |> get("/api/v1/lists")
+
+    assert [
+             %{"id" => _, "title" => "cofe"},
+             %{"id" => _, "title" => "cuties"}
+           ] = json_response(conn, :ok)
+  end
+
+  test "adding users to a list", %{conn: conn} do
+    user = insert(:user)
+    other_user = insert(:user)
+    {:ok, list} = Pleroma.List.create("name", user)
+
+    conn =
+      conn
+      |> assign(:user, user)
+      |> post("/api/v1/lists/#{list.id}/accounts", %{"account_ids" => [other_user.id]})
+
+    assert %{} == json_response(conn, 200)
+    %Pleroma.List{following: following} = Pleroma.List.get(list.id, user)
+    assert following == [other_user.follower_address]
+  end
+
+  test "removing users from a list", %{conn: conn} do
+    user = insert(:user)
+    other_user = insert(:user)
+    third_user = insert(:user)
+    {:ok, list} = Pleroma.List.create("name", user)
+    {:ok, list} = Pleroma.List.follow(list, other_user)
+    {:ok, list} = Pleroma.List.follow(list, third_user)
+
+    conn =
+      conn
+      |> assign(:user, user)
+      |> delete("/api/v1/lists/#{list.id}/accounts", %{"account_ids" => [other_user.id]})
+
+    assert %{} == json_response(conn, 200)
+    %Pleroma.List{following: following} = Pleroma.List.get(list.id, user)
+    assert following == [third_user.follower_address]
+  end
+
+  test "listing users in a list", %{conn: conn} do
+    user = insert(:user)
+    other_user = insert(:user)
+    {:ok, list} = Pleroma.List.create("name", user)
+    {:ok, list} = Pleroma.List.follow(list, other_user)
+
+    conn =
+      conn
+      |> assign(:user, user)
+      |> get("/api/v1/lists/#{list.id}/accounts", %{"account_ids" => [other_user.id]})
+
+    assert [%{"id" => id}] = json_response(conn, 200)
+    assert id == to_string(other_user.id)
+  end
+
+  test "retrieving a list", %{conn: conn} do
+    user = insert(:user)
+    {:ok, list} = Pleroma.List.create("name", user)
+
+    conn =
+      conn
+      |> assign(:user, user)
+      |> get("/api/v1/lists/#{list.id}")
+
+    assert %{"id" => id} = json_response(conn, 200)
+    assert id == to_string(list.id)
+  end
+
+  test "renders 404 if list is not found", %{conn: conn} do
+    user = insert(:user)
+
+    conn =
+      conn
+      |> assign(:user, user)
+      |> get("/api/v1/lists/666")
+
+    assert %{"error" => "List not found"} = json_response(conn, :not_found)
+  end
+
+  test "renaming a list", %{conn: conn} do
+    user = insert(:user)
+    {:ok, list} = Pleroma.List.create("name", user)
+
+    conn =
+      conn
+      |> assign(:user, user)
+      |> put("/api/v1/lists/#{list.id}", %{"title" => "newname"})
+
+    assert %{"title" => name} = json_response(conn, 200)
+    assert name == "newname"
+  end
+
+  test "validates title when renaming a list", %{conn: conn} do
+    user = insert(:user)
+    {:ok, list} = Pleroma.List.create("name", user)
+
+    conn =
+      conn
+      |> assign(:user, user)
+      |> put("/api/v1/lists/#{list.id}", %{"title" => "  "})
+
+    assert %{"error" => "can't be blank"} == json_response(conn, :unprocessable_entity)
+  end
+
+  test "deleting a list", %{conn: conn} do
+    user = insert(:user)
+    {:ok, list} = Pleroma.List.create("name", user)
+
+    conn =
+      conn
+      |> assign(:user, user)
+      |> delete("/api/v1/lists/#{list.id}")
+
+    assert %{} = json_response(conn, 200)
+    assert is_nil(Repo.get(Pleroma.List, list.id))
+  end
+end
diff --git a/test/web/mastodon_api/mastodon_api_controller_test.exs b/test/web/mastodon_api/mastodon_api_controller_test.exs
index 6fcdc19aa..4fd0a5aeb 100644
--- a/test/web/mastodon_api/mastodon_api_controller_test.exs
+++ b/test/web/mastodon_api/mastodon_api_controller_test.exs
@@ -927,106 +927,7 @@ test "delete a filter", %{conn: conn} do
     end
   end
 
-  describe "lists" do
-    test "creating a list", %{conn: conn} do
-      user = insert(:user)
-
-      conn =
-        conn
-        |> assign(:user, user)
-        |> post("/api/v1/lists", %{"title" => "cuties"})
-
-      assert %{"title" => title} = json_response(conn, 200)
-      assert title == "cuties"
-    end
-
-    test "adding users to a list", %{conn: conn} do
-      user = insert(:user)
-      other_user = insert(:user)
-      {:ok, list} = Pleroma.List.create("name", user)
-
-      conn =
-        conn
-        |> assign(:user, user)
-        |> post("/api/v1/lists/#{list.id}/accounts", %{"account_ids" => [other_user.id]})
-
-      assert %{} == json_response(conn, 200)
-      %Pleroma.List{following: following} = Pleroma.List.get(list.id, user)
-      assert following == [other_user.follower_address]
-    end
-
-    test "removing users from a list", %{conn: conn} do
-      user = insert(:user)
-      other_user = insert(:user)
-      third_user = insert(:user)
-      {:ok, list} = Pleroma.List.create("name", user)
-      {:ok, list} = Pleroma.List.follow(list, other_user)
-      {:ok, list} = Pleroma.List.follow(list, third_user)
-
-      conn =
-        conn
-        |> assign(:user, user)
-        |> delete("/api/v1/lists/#{list.id}/accounts", %{"account_ids" => [other_user.id]})
-
-      assert %{} == json_response(conn, 200)
-      %Pleroma.List{following: following} = Pleroma.List.get(list.id, user)
-      assert following == [third_user.follower_address]
-    end
-
-    test "listing users in a list", %{conn: conn} do
-      user = insert(:user)
-      other_user = insert(:user)
-      {:ok, list} = Pleroma.List.create("name", user)
-      {:ok, list} = Pleroma.List.follow(list, other_user)
-
-      conn =
-        conn
-        |> assign(:user, user)
-        |> get("/api/v1/lists/#{list.id}/accounts", %{"account_ids" => [other_user.id]})
-
-      assert [%{"id" => id}] = json_response(conn, 200)
-      assert id == to_string(other_user.id)
-    end
-
-    test "retrieving a list", %{conn: conn} do
-      user = insert(:user)
-      {:ok, list} = Pleroma.List.create("name", user)
-
-      conn =
-        conn
-        |> assign(:user, user)
-        |> get("/api/v1/lists/#{list.id}")
-
-      assert %{"id" => id} = json_response(conn, 200)
-      assert id == to_string(list.id)
-    end
-
-    test "renaming a list", %{conn: conn} do
-      user = insert(:user)
-      {:ok, list} = Pleroma.List.create("name", user)
-
-      conn =
-        conn
-        |> assign(:user, user)
-        |> put("/api/v1/lists/#{list.id}", %{"title" => "newname"})
-
-      assert %{"title" => name} = json_response(conn, 200)
-      assert name == "newname"
-    end
-
-    test "deleting a list", %{conn: conn} do
-      user = insert(:user)
-      {:ok, list} = Pleroma.List.create("name", user)
-
-      conn =
-        conn
-        |> assign(:user, user)
-        |> delete("/api/v1/lists/#{list.id}")
-
-      assert %{} = json_response(conn, 200)
-      assert is_nil(Repo.get(Pleroma.List, list.id))
-    end
-
+  describe "list timelines" do
     test "list timeline", %{conn: conn} do
       user = insert(:user)
       other_user = insert(:user)
diff --git a/test/web/mastodon_api/list_view_test.exs b/test/web/mastodon_api/views/list_view_test.exs
similarity index 56%
rename from test/web/mastodon_api/list_view_test.exs
rename to test/web/mastodon_api/views/list_view_test.exs
index 73143467f..fb00310b9 100644
--- a/test/web/mastodon_api/list_view_test.exs
+++ b/test/web/mastodon_api/views/list_view_test.exs
@@ -7,7 +7,7 @@ defmodule Pleroma.Web.MastodonAPI.ListViewTest do
   import Pleroma.Factory
   alias Pleroma.Web.MastodonAPI.ListView
 
-  test "Represent a list" do
+  test "show" do
     user = insert(:user)
     title = "mortal enemies"
     {:ok, list} = Pleroma.List.create(title, user)
@@ -17,6 +17,16 @@ test "Represent a list" do
       title: title
     }
 
-    assert expected == ListView.render("list.json", %{list: list})
+    assert expected == ListView.render("show.json", %{list: list})
+  end
+
+  test "index" do
+    user = insert(:user)
+
+    {:ok, list} = Pleroma.List.create("my list", user)
+    {:ok, list2} = Pleroma.List.create("cofe", user)
+
+    assert [%{id: _, title: "my list"}, %{id: _, title: "cofe"}] =
+             ListView.render("index.json", lists: [list, list2])
   end
 end

From 4194abbc8fbc8003d9923edaa491e798bea92107 Mon Sep 17 00:00:00 2001
From: Egor Kislitsyn <egor@kislitsyn.com>
Date: Mon, 26 Aug 2019 19:32:47 +0700
Subject: [PATCH 4/5] Move mastodon_api/*_controller.ex to
 mastodon_api/controllers/

---
 .../mastodon_api_controller.ex                | 20 +++++++++----------
 .../{ => controllers}/search_controller.ex    |  0
 .../subscription_controller.ex                |  0
 3 files changed, 10 insertions(+), 10 deletions(-)
 rename lib/pleroma/web/mastodon_api/{ => controllers}/mastodon_api_controller.ex (98%)
 rename lib/pleroma/web/mastodon_api/{ => controllers}/search_controller.ex (100%)
 rename lib/pleroma/web/mastodon_api/{ => controllers}/subscription_controller.ex (100%)

diff --git a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex b/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex
similarity index 98%
rename from lib/pleroma/web/mastodon_api/mastodon_api_controller.ex
rename to lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex
index 31b0aaca0..83e877c0e 100644
--- a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex
@@ -189,7 +189,7 @@ def update_credentials(%{assigns: %{user: user}} = conn, params) do
     info_cng = User.Info.profile_update(user.info, info_params)
 
     with changeset <- User.update_changeset(user, user_params),
-         changeset <- Ecto.Changeset.put_embed(changeset, :info, info_cng),
+         changeset <- Changeset.put_embed(changeset, :info, info_cng),
          {:ok, user} <- User.update_and_set_cache(changeset) do
       if original_user != user do
         CommonAPI.update(user)
@@ -225,7 +225,7 @@ def update_avatar(%{assigns: %{user: user}} = conn, params) do
   def update_banner(%{assigns: %{user: user}} = conn, %{"banner" => ""}) do
     with new_info <- %{"banner" => %{}},
          info_cng <- User.Info.profile_update(user.info, new_info),
-         changeset <- Ecto.Changeset.change(user) |> Ecto.Changeset.put_embed(:info, info_cng),
+         changeset <- Changeset.change(user) |> Changeset.put_embed(:info, info_cng),
          {:ok, user} <- User.update_and_set_cache(changeset) do
       CommonAPI.update(user)
 
@@ -237,7 +237,7 @@ def update_banner(%{assigns: %{user: user}} = conn, params) do
     with {:ok, object} <- ActivityPub.upload(%{"img" => params["banner"]}, type: :banner),
          new_info <- %{"banner" => object.data},
          info_cng <- User.Info.profile_update(user.info, new_info),
-         changeset <- Ecto.Changeset.change(user) |> Ecto.Changeset.put_embed(:info, info_cng),
+         changeset <- Changeset.change(user) |> Changeset.put_embed(:info, info_cng),
          {:ok, user} <- User.update_and_set_cache(changeset) do
       CommonAPI.update(user)
       %{"url" => [%{"href" => href} | _]} = object.data
@@ -249,7 +249,7 @@ def update_banner(%{assigns: %{user: user}} = conn, params) do
   def update_background(%{assigns: %{user: user}} = conn, %{"img" => ""}) do
     with new_info <- %{"background" => %{}},
          info_cng <- User.Info.profile_update(user.info, new_info),
-         changeset <- Ecto.Changeset.change(user) |> Ecto.Changeset.put_embed(:info, info_cng),
+         changeset <- Changeset.change(user) |> Changeset.put_embed(:info, info_cng),
          {:ok, _user} <- User.update_and_set_cache(changeset) do
       json(conn, %{url: nil})
     end
@@ -259,7 +259,7 @@ def update_background(%{assigns: %{user: user}} = conn, params) do
     with {:ok, object} <- ActivityPub.upload(params, type: :background),
          new_info <- %{"background" => object.data},
          info_cng <- User.Info.profile_update(user.info, new_info),
-         changeset <- Ecto.Changeset.change(user) |> Ecto.Changeset.put_embed(:info, info_cng),
+         changeset <- Changeset.change(user) |> Changeset.put_embed(:info, info_cng),
          {:ok, _user} <- User.update_and_set_cache(changeset) do
       %{"url" => [%{"href" => href} | _]} = object.data
 
@@ -806,8 +806,8 @@ def set_mascot(%{assigns: %{user: user}} = conn, %{"file" => file}) do
 
         user_changeset =
           user
-          |> Ecto.Changeset.change()
-          |> Ecto.Changeset.put_embed(:info, info_changeset)
+          |> Changeset.change()
+          |> Changeset.put_embed(:info, info_changeset)
 
         {:ok, _user} = User.update_and_set_cache(user_changeset)
 
@@ -1344,8 +1344,8 @@ def index(%{assigns: %{user: user}} = conn, _params) do
   def put_settings(%{assigns: %{user: user}} = conn, %{"data" => settings} = _params) do
     info_cng = User.Info.mastodon_settings_update(user.info, settings)
 
-    with changeset <- Ecto.Changeset.change(user),
-         changeset <- Ecto.Changeset.put_embed(changeset, :info, info_cng),
+    with changeset <- Changeset.change(user),
+         changeset <- Changeset.put_embed(changeset, :info, info_cng),
          {:ok, _user} <- User.update_and_set_cache(changeset) do
       json(conn, %{})
     else
@@ -1409,7 +1409,7 @@ defp get_or_make_app do
           {:ok, app}
         else
           app
-          |> Ecto.Changeset.change(%{scopes: scopes})
+          |> Changeset.change(%{scopes: scopes})
           |> Repo.update()
         end
 
diff --git a/lib/pleroma/web/mastodon_api/search_controller.ex b/lib/pleroma/web/mastodon_api/controllers/search_controller.ex
similarity index 100%
rename from lib/pleroma/web/mastodon_api/search_controller.ex
rename to lib/pleroma/web/mastodon_api/controllers/search_controller.ex
diff --git a/lib/pleroma/web/mastodon_api/subscription_controller.ex b/lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex
similarity index 100%
rename from lib/pleroma/web/mastodon_api/subscription_controller.ex
rename to lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex

From 019ced055836b3d01ea95865549478dc5cdb3c0e Mon Sep 17 00:00:00 2001
From: Egor Kislitsyn <egor@kislitsyn.com>
Date: Mon, 26 Aug 2019 19:34:43 +0700
Subject: [PATCH 5/5] Move test/web/mastodon_api/*_test.exs to
 test/web/mastodon_api/controllers and test/web/mastodon_api/views

---
 .../mastodon_api_controller/update_credentials_test.exs           | 0
 .../web/mastodon_api/{ => controllers}/search_controller_test.exs | 0
 .../{ => controllers}/subscription_controller_test.exs            | 0
 test/web/mastodon_api/{ => views}/account_view_test.exs           | 0
 test/web/mastodon_api/{ => views}/conversation_view_test.exs      | 0
 test/web/mastodon_api/{ => views}/notification_view_test.exs      | 0
 test/web/mastodon_api/{ => views}/push_subscription_view_test.exs | 0
 .../web/mastodon_api/{ => views}/scheduled_activity_view_test.exs | 0
 test/web/mastodon_api/{ => views}/status_view_test.exs            | 0
 9 files changed, 0 insertions(+), 0 deletions(-)
 rename test/web/mastodon_api/{ => controllers}/mastodon_api_controller/update_credentials_test.exs (100%)
 rename test/web/mastodon_api/{ => controllers}/search_controller_test.exs (100%)
 rename test/web/mastodon_api/{ => controllers}/subscription_controller_test.exs (100%)
 rename test/web/mastodon_api/{ => views}/account_view_test.exs (100%)
 rename test/web/mastodon_api/{ => views}/conversation_view_test.exs (100%)
 rename test/web/mastodon_api/{ => views}/notification_view_test.exs (100%)
 rename test/web/mastodon_api/{ => views}/push_subscription_view_test.exs (100%)
 rename test/web/mastodon_api/{ => views}/scheduled_activity_view_test.exs (100%)
 rename test/web/mastodon_api/{ => views}/status_view_test.exs (100%)

diff --git a/test/web/mastodon_api/mastodon_api_controller/update_credentials_test.exs b/test/web/mastodon_api/controllers/mastodon_api_controller/update_credentials_test.exs
similarity index 100%
rename from test/web/mastodon_api/mastodon_api_controller/update_credentials_test.exs
rename to test/web/mastodon_api/controllers/mastodon_api_controller/update_credentials_test.exs
diff --git a/test/web/mastodon_api/search_controller_test.exs b/test/web/mastodon_api/controllers/search_controller_test.exs
similarity index 100%
rename from test/web/mastodon_api/search_controller_test.exs
rename to test/web/mastodon_api/controllers/search_controller_test.exs
diff --git a/test/web/mastodon_api/subscription_controller_test.exs b/test/web/mastodon_api/controllers/subscription_controller_test.exs
similarity index 100%
rename from test/web/mastodon_api/subscription_controller_test.exs
rename to test/web/mastodon_api/controllers/subscription_controller_test.exs
diff --git a/test/web/mastodon_api/account_view_test.exs b/test/web/mastodon_api/views/account_view_test.exs
similarity index 100%
rename from test/web/mastodon_api/account_view_test.exs
rename to test/web/mastodon_api/views/account_view_test.exs
diff --git a/test/web/mastodon_api/conversation_view_test.exs b/test/web/mastodon_api/views/conversation_view_test.exs
similarity index 100%
rename from test/web/mastodon_api/conversation_view_test.exs
rename to test/web/mastodon_api/views/conversation_view_test.exs
diff --git a/test/web/mastodon_api/notification_view_test.exs b/test/web/mastodon_api/views/notification_view_test.exs
similarity index 100%
rename from test/web/mastodon_api/notification_view_test.exs
rename to test/web/mastodon_api/views/notification_view_test.exs
diff --git a/test/web/mastodon_api/push_subscription_view_test.exs b/test/web/mastodon_api/views/push_subscription_view_test.exs
similarity index 100%
rename from test/web/mastodon_api/push_subscription_view_test.exs
rename to test/web/mastodon_api/views/push_subscription_view_test.exs
diff --git a/test/web/mastodon_api/scheduled_activity_view_test.exs b/test/web/mastodon_api/views/scheduled_activity_view_test.exs
similarity index 100%
rename from test/web/mastodon_api/scheduled_activity_view_test.exs
rename to test/web/mastodon_api/views/scheduled_activity_view_test.exs
diff --git a/test/web/mastodon_api/status_view_test.exs b/test/web/mastodon_api/views/status_view_test.exs
similarity index 100%
rename from test/web/mastodon_api/status_view_test.exs
rename to test/web/mastodon_api/views/status_view_test.exs