Merge remote-tracking branch 'pleroma/develop' into feature/disable-account
[akkoma] / test / web / admin_api / admin_api_controller_test.exs
index acae643614dcf1ea265ca5ed3ca5e3fe32867c4b..6c1897b5a9354336d00fe2df857abf0a68ff0331 100644 (file)
@@ -6,9 +6,10 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
   use Pleroma.Web.ConnCase
 
   alias Pleroma.User
+  alias Pleroma.UserInviteToken
   import Pleroma.Factory
 
-  describe "/api/pleroma/admin/user" do
+  describe "/api/pleroma/admin/users" do
     test "Delete" do
       admin = insert(:user, info: %{is_admin: true})
       user = insert(:user)
@@ -17,7 +18,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
         build_conn()
         |> assign(:user, admin)
         |> put_req_header("accept", "application/json")
-        |> delete("/api/pleroma/admin/user?nickname=#{user.nickname}")
+        |> delete("/api/pleroma/admin/users?nickname=#{user.nickname}")
 
       assert json_response(conn, 200) == user.nickname
     end
@@ -29,7 +30,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
         build_conn()
         |> assign(:user, admin)
         |> put_req_header("accept", "application/json")
-        |> post("/api/pleroma/admin/user", %{
+        |> post("/api/pleroma/admin/users", %{
           "nickname" => "lain",
           "email" => "lain@example.org",
           "password" => "test"
@@ -74,6 +75,50 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
     end
   end
 
+  describe "/api/pleroma/admin/users/follow" do
+    test "allows to force-follow another user" do
+      admin = insert(:user, info: %{is_admin: true})
+      user = insert(:user)
+      follower = insert(:user)
+
+      build_conn()
+      |> assign(:user, admin)
+      |> put_req_header("accept", "application/json")
+      |> post("/api/pleroma/admin/users/follow", %{
+        "follower" => follower.nickname,
+        "followed" => user.nickname
+      })
+
+      user = User.get_cached_by_id(user.id)
+      follower = User.get_cached_by_id(follower.id)
+
+      assert User.following?(follower, user)
+    end
+  end
+
+  describe "/api/pleroma/admin/users/unfollow" do
+    test "allows to force-unfollow another user" do
+      admin = insert(:user, info: %{is_admin: true})
+      user = insert(:user)
+      follower = insert(:user)
+
+      User.follow(follower, user)
+
+      build_conn()
+      |> assign(:user, admin)
+      |> put_req_header("accept", "application/json")
+      |> post("/api/pleroma/admin/users/unfollow", %{
+        "follower" => follower.nickname,
+        "followed" => user.nickname
+      })
+
+      user = User.get_cached_by_id(user.id)
+      follower = User.get_cached_by_id(follower.id)
+
+      refute User.following?(follower, user)
+    end
+  end
+
   describe "PUT /api/pleroma/admin/users/tag" do
     setup do
       admin = insert(:user, info: %{is_admin: true})
@@ -100,13 +145,13 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
       user2: user2
     } do
       assert json_response(conn, :no_content)
-      assert User.get_by_id(user1.id).tags == ["x", "foo", "bar"]
-      assert User.get_by_id(user2.id).tags == ["y", "foo", "bar"]
+      assert User.get_cached_by_id(user1.id).tags == ["x", "foo", "bar"]
+      assert User.get_cached_by_id(user2.id).tags == ["y", "foo", "bar"]
     end
 
     test "it does not modify tags of not specified users", %{conn: conn, user3: user3} do
       assert json_response(conn, :no_content)
-      assert User.get_by_id(user3.id).tags == ["unchanged"]
+      assert User.get_cached_by_id(user3.id).tags == ["unchanged"]
     end
   end
 
@@ -136,17 +181,17 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
       user2: user2
     } do
       assert json_response(conn, :no_content)
-      assert User.get_by_id(user1.id).tags == []
-      assert User.get_by_id(user2.id).tags == ["y"]
+      assert User.get_cached_by_id(user1.id).tags == []
+      assert User.get_cached_by_id(user2.id).tags == ["y"]
     end
 
     test "it does not modify tags of not specified users", %{conn: conn, user3: user3} do
       assert json_response(conn, :no_content)
-      assert User.get_by_id(user3.id).tags == ["unchanged"]
+      assert User.get_cached_by_id(user3.id).tags == ["unchanged"]
     end
   end
 
-  describe "/api/pleroma/admin/permission_group" do
+  describe "/api/pleroma/admin/users/:nickname/permission_group" do
     test "GET is giving user_info" do
       admin = insert(:user, info: %{is_admin: true})
 
@@ -154,7 +199,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
         build_conn()
         |> assign(:user, admin)
         |> put_req_header("accept", "application/json")
-        |> get("/api/pleroma/admin/permission_group/#{admin.nickname}")
+        |> get("/api/pleroma/admin/users/#{admin.nickname}/permission_group/")
 
       assert json_response(conn, 200) == %{
                "is_admin" => true,
@@ -170,7 +215,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
         build_conn()
         |> assign(:user, admin)
         |> put_req_header("accept", "application/json")
-        |> post("/api/pleroma/admin/permission_group/#{user.nickname}/admin")
+        |> post("/api/pleroma/admin/users/#{user.nickname}/permission_group/admin")
 
       assert json_response(conn, 200) == %{
                "is_admin" => true
@@ -185,7 +230,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
         build_conn()
         |> assign(:user, admin)
         |> put_req_header("accept", "application/json")
-        |> delete("/api/pleroma/admin/permission_group/#{user.nickname}/admin")
+        |> delete("/api/pleroma/admin/users/#{user.nickname}/permission_group/admin")
 
       assert json_response(conn, 200) == %{
                "is_admin" => false
@@ -193,7 +238,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
     end
   end
 
-  describe "PUT /api/pleroma/admin/activation_status" do
+  describe "PUT /api/pleroma/admin/users/:nickname/activation_status" do
     setup %{conn: conn} do
       admin = insert(:user, info: %{is_admin: true})
 
@@ -210,9 +255,9 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
 
       conn =
         conn
-        |> put("/api/pleroma/admin/activation_status/#{user.nickname}", %{status: false})
+        |> put("/api/pleroma/admin/users/#{user.nickname}/activation_status", %{status: false})
 
-      user = User.get_by_id(user.id)
+      user = User.get_cached_by_id(user.id)
       assert user.info.deactivated == true
       assert json_response(conn, :no_content)
     end
@@ -222,9 +267,9 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
 
       conn =
         conn
-        |> put("/api/pleroma/admin/activation_status/#{user.nickname}", %{status: true})
+        |> put("/api/pleroma/admin/users/#{user.nickname}/activation_status", %{status: true})
 
-      user = User.get_by_id(user.id)
+      user = User.get_cached_by_id(user.id)
       assert user.info.deactivated == false
       assert json_response(conn, :no_content)
     end
@@ -235,7 +280,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
       conn =
         conn
         |> assign(:user, user)
-        |> put("/api/pleroma/admin/activation_status/#{user.nickname}", %{status: false})
+        |> put("/api/pleroma/admin/users/#{user.nickname}/activation_status", %{status: false})
 
       assert json_response(conn, :forbidden)
     end
@@ -264,7 +309,9 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
       conn =
         conn
         |> assign(:user, user)
-        |> post("/api/pleroma/admin/email_invite?email=#{recipient_email}&name=#{recipient_name}")
+        |> post(
+          "/api/pleroma/admin/users/email_invite?email=#{recipient_email}&name=#{recipient_name}"
+        )
 
       assert json_response(conn, :no_content)
 
@@ -272,13 +319,21 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
       assert token_record
       refute token_record.used
 
-      Swoosh.TestAssertions.assert_email_sent(
-        Pleroma.UserEmail.user_invitation_email(
+      notify_email = Pleroma.Config.get([:instance, :notify_email])
+      instance_name = Pleroma.Config.get([:instance, :name])
+
+      email =
+        Pleroma.Emails.UserEmail.user_invitation_email(
           user,
           token_record,
           recipient_email,
           recipient_name
         )
+
+      Swoosh.TestAssertions.assert_email_sent(
+        from: {instance_name, notify_email},
+        to: {recipient_name, recipient_email},
+        html_body: email.html_body
       )
     end
 
@@ -288,13 +343,13 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
       conn =
         conn
         |> assign(:user, non_admin_user)
-        |> post("/api/pleroma/admin/email_invite?email=foo@bar.com&name=JD")
+        |> post("/api/pleroma/admin/users/email_invite?email=foo@bar.com&name=JD")
 
       assert json_response(conn, :forbidden)
     end
   end
 
-  describe "POST /api/pleroma/admin/email_invite, with invalid config" do
+  describe "POST /api/pleroma/admin/users/email_invite, with invalid config" do
     setup do
       [user: insert(:user, info: %{is_admin: true})]
     end
@@ -314,7 +369,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
       conn =
         conn
         |> assign(:user, user)
-        |> post("/api/pleroma/admin/email_invite?email=foo@bar.com&name=JD")
+        |> post("/api/pleroma/admin/users/email_invite?email=foo@bar.com&name=JD")
 
       assert json_response(conn, :internal_server_error)
     end
@@ -334,7 +389,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
       conn =
         conn
         |> assign(:user, user)
-        |> post("/api/pleroma/admin/email_invite?email=foo@bar.com&name=JD")
+        |> post("/api/pleroma/admin/users/email_invite?email=foo@bar.com&name=JD")
 
       assert json_response(conn, :internal_server_error)
     end
@@ -352,7 +407,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
     assert conn.status == 200
   end
 
-  test "/api/pleroma/admin/password_reset" do
+  test "/api/pleroma/admin/users/:nickname/password_reset" do
     admin = insert(:user, info: %{is_admin: true})
     user = insert(:user)
 
@@ -360,20 +415,25 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
       build_conn()
       |> assign(:user, admin)
       |> put_req_header("accept", "application/json")
-      |> get("/api/pleroma/admin/password_reset?nickname=#{user.nickname}")
+      |> get("/api/pleroma/admin/users/#{user.nickname}/password_reset")
 
     assert conn.status == 200
   end
 
   describe "GET /api/pleroma/admin/users" do
-    test "renders users array for the first page" do
+    setup do
       admin = insert(:user, info: %{is_admin: true})
-      user = insert(:user, local: false, tags: ["foo", "bar"])
 
       conn =
         build_conn()
         |> assign(:user, admin)
-        |> get("/api/pleroma/admin/users?page=1")
+
+      {:ok, conn: conn, admin: admin}
+    end
+
+    test "renders users array for the first page", %{conn: conn, admin: admin} do
+      user = insert(:user, local: false, tags: ["foo", "bar"])
+      conn = get(conn, "/api/pleroma/admin/users?page=1")
 
       assert json_response(conn, 200) == %{
                "count" => 2,
@@ -399,14 +459,10 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
              }
     end
 
-    test "renders empty array for the second page" do
-      admin = insert(:user, info: %{is_admin: true})
+    test "renders empty array for the second page", %{conn: conn} do
       insert(:user)
 
-      conn =
-        build_conn()
-        |> assign(:user, admin)
-        |> get("/api/pleroma/admin/users?page=2")
+      conn = get(conn, "/api/pleroma/admin/users?page=2")
 
       assert json_response(conn, 200) == %{
                "count" => 2,
@@ -415,14 +471,10 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
              }
     end
 
-    test "regular search" do
-      admin = insert(:user, info: %{is_admin: true})
+    test "regular search", %{conn: conn} do
       user = insert(:user, nickname: "bob")
 
-      conn =
-        build_conn()
-        |> assign(:user, admin)
-        |> get("/api/pleroma/admin/users?query=bo")
+      conn = get(conn, "/api/pleroma/admin/users?query=bo")
 
       assert json_response(conn, 200) == %{
                "count" => 1,
@@ -440,17 +492,101 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
              }
     end
 
-    test "regular search with page size" do
-      admin = insert(:user, info: %{is_admin: true})
+    test "search by domain", %{conn: conn} do
+      user = insert(:user, nickname: "nickname@domain.com")
+      insert(:user)
+
+      conn = get(conn, "/api/pleroma/admin/users?query=domain.com")
+
+      assert json_response(conn, 200) == %{
+               "count" => 1,
+               "page_size" => 50,
+               "users" => [
+                 %{
+                   "deactivated" => user.info.deactivated,
+                   "id" => user.id,
+                   "nickname" => user.nickname,
+                   "roles" => %{"admin" => false, "moderator" => false},
+                   "local" => true,
+                   "tags" => []
+                 }
+               ]
+             }
+    end
+
+    test "search by full nickname", %{conn: conn} do
+      user = insert(:user, nickname: "nickname@domain.com")
+      insert(:user)
+
+      conn = get(conn, "/api/pleroma/admin/users?query=nickname@domain.com")
+
+      assert json_response(conn, 200) == %{
+               "count" => 1,
+               "page_size" => 50,
+               "users" => [
+                 %{
+                   "deactivated" => user.info.deactivated,
+                   "id" => user.id,
+                   "nickname" => user.nickname,
+                   "roles" => %{"admin" => false, "moderator" => false},
+                   "local" => true,
+                   "tags" => []
+                 }
+               ]
+             }
+    end
+
+    test "search by display name", %{conn: conn} do
+      user = insert(:user, name: "Display name")
+      insert(:user)
+
+      conn = get(conn, "/api/pleroma/admin/users?name=display")
+
+      assert json_response(conn, 200) == %{
+               "count" => 1,
+               "page_size" => 50,
+               "users" => [
+                 %{
+                   "deactivated" => user.info.deactivated,
+                   "id" => user.id,
+                   "nickname" => user.nickname,
+                   "roles" => %{"admin" => false, "moderator" => false},
+                   "local" => true,
+                   "tags" => []
+                 }
+               ]
+             }
+    end
+
+    test "search by email", %{conn: conn} do
+      user = insert(:user, email: "email@example.com")
+      insert(:user)
+
+      conn = get(conn, "/api/pleroma/admin/users?email=email@example.com")
+
+      assert json_response(conn, 200) == %{
+               "count" => 1,
+               "page_size" => 50,
+               "users" => [
+                 %{
+                   "deactivated" => user.info.deactivated,
+                   "id" => user.id,
+                   "nickname" => user.nickname,
+                   "roles" => %{"admin" => false, "moderator" => false},
+                   "local" => true,
+                   "tags" => []
+                 }
+               ]
+             }
+    end
+
+    test "regular search with page size", %{conn: conn} do
       user = insert(:user, nickname: "aalice")
       user2 = insert(:user, nickname: "alice")
 
-      conn =
-        build_conn()
-        |> assign(:user, admin)
-        |> get("/api/pleroma/admin/users?query=a&page_size=1&page=1")
+      conn1 = get(conn, "/api/pleroma/admin/users?query=a&page_size=1&page=1")
 
-      assert json_response(conn, 200) == %{
+      assert json_response(conn1, 200) == %{
                "count" => 2,
                "page_size" => 1,
                "users" => [
@@ -465,12 +601,9 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
                ]
              }
 
-      conn =
-        build_conn()
-        |> assign(:user, admin)
-        |> get("/api/pleroma/admin/users?query=a&page_size=1&page=2")
+      conn2 = get(conn, "/api/pleroma/admin/users?query=a&page_size=1&page=2")
 
-      assert json_response(conn, 200) == %{
+      assert json_response(conn2, 200) == %{
                "count" => 2,
                "page_size" => 1,
                "users" => [
@@ -513,7 +646,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
              }
     end
 
-    test "only local users with no query" do
+    test "only local users with no query", %{admin: old_admin} do
       admin = insert(:user, info: %{is_admin: true}, nickname: "john")
       user = insert(:user, nickname: "bob")
 
@@ -525,7 +658,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
         |> get("/api/pleroma/admin/users?filters=local")
 
       assert json_response(conn, 200) == %{
-               "count" => 2,
+               "count" => 3,
                "page_size" => 50,
                "users" => [
                  %{
@@ -543,6 +676,100 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
                    "roles" => %{"admin" => true, "moderator" => false},
                    "local" => true,
                    "tags" => []
+                 },
+                 %{
+                   "deactivated" => false,
+                   "id" => old_admin.id,
+                   "local" => true,
+                   "nickname" => old_admin.nickname,
+                   "roles" => %{"admin" => true, "moderator" => false},
+                   "tags" => []
+                 }
+               ]
+             }
+    end
+
+    test "load only admins", %{conn: conn, admin: admin} do
+      second_admin = insert(:user, info: %{is_admin: true})
+      insert(:user)
+      insert(:user)
+
+      conn = get(conn, "/api/pleroma/admin/users?filters=is_admin")
+
+      assert json_response(conn, 200) == %{
+               "count" => 2,
+               "page_size" => 50,
+               "users" => [
+                 %{
+                   "deactivated" => false,
+                   "id" => admin.id,
+                   "nickname" => admin.nickname,
+                   "roles" => %{"admin" => true, "moderator" => false},
+                   "local" => admin.local,
+                   "tags" => []
+                 },
+                 %{
+                   "deactivated" => false,
+                   "id" => second_admin.id,
+                   "nickname" => second_admin.nickname,
+                   "roles" => %{"admin" => true, "moderator" => false},
+                   "local" => second_admin.local,
+                   "tags" => []
+                 }
+               ]
+             }
+    end
+
+    test "load only moderators", %{conn: conn} do
+      moderator = insert(:user, info: %{is_moderator: true})
+      insert(:user)
+      insert(:user)
+
+      conn = get(conn, "/api/pleroma/admin/users?filters=is_moderator")
+
+      assert json_response(conn, 200) == %{
+               "count" => 1,
+               "page_size" => 50,
+               "users" => [
+                 %{
+                   "deactivated" => false,
+                   "id" => moderator.id,
+                   "nickname" => moderator.nickname,
+                   "roles" => %{"admin" => false, "moderator" => true},
+                   "local" => moderator.local,
+                   "tags" => []
+                 }
+               ]
+             }
+    end
+
+    test "load users with tags list", %{conn: conn} do
+      user1 = insert(:user, tags: ["first"])
+      user2 = insert(:user, tags: ["second"])
+      insert(:user)
+      insert(:user)
+
+      conn = get(conn, "/api/pleroma/admin/users?tags[]=first&tags[]=second")
+
+      assert json_response(conn, 200) == %{
+               "count" => 2,
+               "page_size" => 50,
+               "users" => [
+                 %{
+                   "deactivated" => false,
+                   "id" => user1.id,
+                   "nickname" => user1.nickname,
+                   "roles" => %{"admin" => false, "moderator" => false},
+                   "local" => user1.local,
+                   "tags" => ["first"]
+                 },
+                 %{
+                   "deactivated" => false,
+                   "id" => user2.id,
+                   "nickname" => user2.nickname,
+                   "roles" => %{"admin" => false, "moderator" => false},
+                   "local" => user2.local,
+                   "tags" => ["second"]
                  }
                ]
              }
@@ -596,4 +823,130 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
                "tags" => []
              }
   end
+
+  describe "GET /api/pleroma/admin/users/invite_token" do
+    setup do
+      admin = insert(:user, info: %{is_admin: true})
+
+      conn =
+        build_conn()
+        |> assign(:user, admin)
+
+      {:ok, conn: conn}
+    end
+
+    test "without options", %{conn: conn} do
+      conn = get(conn, "/api/pleroma/admin/users/invite_token")
+
+      token = json_response(conn, 200)
+      invite = UserInviteToken.find_by_token!(token)
+      refute invite.used
+      refute invite.expires_at
+      refute invite.max_use
+      assert invite.invite_type == "one_time"
+    end
+
+    test "with expires_at", %{conn: conn} do
+      conn =
+        get(conn, "/api/pleroma/admin/users/invite_token", %{
+          "invite" => %{"expires_at" => Date.to_string(Date.utc_today())}
+        })
+
+      token = json_response(conn, 200)
+      invite = UserInviteToken.find_by_token!(token)
+
+      refute invite.used
+      assert invite.expires_at == Date.utc_today()
+      refute invite.max_use
+      assert invite.invite_type == "date_limited"
+    end
+
+    test "with max_use", %{conn: conn} do
+      conn =
+        get(conn, "/api/pleroma/admin/users/invite_token", %{
+          "invite" => %{"max_use" => 150}
+        })
+
+      token = json_response(conn, 200)
+      invite = UserInviteToken.find_by_token!(token)
+      refute invite.used
+      refute invite.expires_at
+      assert invite.max_use == 150
+      assert invite.invite_type == "reusable"
+    end
+
+    test "with max use and expires_at", %{conn: conn} do
+      conn =
+        get(conn, "/api/pleroma/admin/users/invite_token", %{
+          "invite" => %{"max_use" => 150, "expires_at" => Date.to_string(Date.utc_today())}
+        })
+
+      token = json_response(conn, 200)
+      invite = UserInviteToken.find_by_token!(token)
+      refute invite.used
+      assert invite.expires_at == Date.utc_today()
+      assert invite.max_use == 150
+      assert invite.invite_type == "reusable_date_limited"
+    end
+  end
+
+  describe "GET /api/pleroma/admin/users/invites" do
+    setup do
+      admin = insert(:user, info: %{is_admin: true})
+
+      conn =
+        build_conn()
+        |> assign(:user, admin)
+
+      {:ok, conn: conn}
+    end
+
+    test "no invites", %{conn: conn} do
+      conn = get(conn, "/api/pleroma/admin/users/invites")
+
+      assert json_response(conn, 200) == %{"invites" => []}
+    end
+
+    test "with invite", %{conn: conn} do
+      {:ok, invite} = UserInviteToken.create_invite()
+
+      conn = get(conn, "/api/pleroma/admin/users/invites")
+
+      assert json_response(conn, 200) == %{
+               "invites" => [
+                 %{
+                   "expires_at" => nil,
+                   "id" => invite.id,
+                   "invite_type" => "one_time",
+                   "max_use" => nil,
+                   "token" => invite.token,
+                   "used" => false,
+                   "uses" => 0
+                 }
+               ]
+             }
+    end
+  end
+
+  describe "POST /api/pleroma/admin/users/revoke_invite" do
+    test "with token" do
+      admin = insert(:user, info: %{is_admin: true})
+      {:ok, invite} = UserInviteToken.create_invite()
+
+      conn =
+        build_conn()
+        |> assign(:user, admin)
+        |> post("/api/pleroma/admin/users/revoke_invite", %{"token" => invite.token})
+
+      assert json_response(conn, 200) == %{
+               "expires_at" => nil,
+               "id" => invite.id,
+               "invite_type" => "one_time",
+               "max_use" => nil,
+               "token" => invite.token,
+               "used" => true,
+               "uses" => 0
+             }
+    end
+  end
 end