From ebb83274f50a63052b6e79a1145efe4eb40413a2 Mon Sep 17 00:00:00 2001 From: njms Date: Thu, 27 Jul 2023 02:00:38 -0700 Subject: [PATCH] Implement controller tests for denizens --- lib/hostas/denizen.ex | 29 ++--- .../controllers/denizen_controller.ex | 4 +- lib/hostas_web/router.ex | 4 +- .../controllers/denizen_controller_test.exs | 110 ++++++++++++++++++ 4 files changed, 129 insertions(+), 18 deletions(-) create mode 100644 test/hostas_web/controllers/denizen_controller_test.exs diff --git a/lib/hostas/denizen.ex b/lib/hostas/denizen.ex index 4ec2901..97fda6f 100644 --- a/lib/hostas/denizen.ex +++ b/lib/hostas/denizen.ex @@ -1,6 +1,10 @@ defmodule Hostas.Denizen do use Ecto.Schema import Ecto.Changeset + import Ecto.Query, only: [from: 2] + + alias Hostas.Denizen + alias Hostas.Repo schema "denizens" do field :handle, :string @@ -29,11 +33,8 @@ defmodule Hostas.Denizen do end defp hash_password(changeset), do: changeset - @doc """ - Returns an Ecto query searching for a denizen by the given ID - """ - defp query_by_id(id) do - from d in Denizen, where: d.id == ^id + defp query_by_handle(handle) do + from d in Denizen, where: d.handle == ^handle end @doc """ @@ -46,20 +47,20 @@ defmodule Hostas.Denizen do @doc """ Returns a denizen """ - def get(id) do - case query_by_id(id) |> Repo.one do + def get(handle) do + case query_by_handle(handle) |> Repo.one do nil -> {:error, :not_found} struct -> {:ok, struct} end end @doc """ - Removes a denizen from the community. Does nothing if the denizen doesn't - exist + deactivates a denizen's account. This should mark their account as deactivated + and replace all their content with tombstones """ - def delete(id) do - id - |> query_by_id + def deactivate(handle) do + handle + |> query_by_handle |> Repo.delete_all :ok @@ -69,7 +70,7 @@ defmodule Hostas.Denizen do Modifies all the denizen's fields except `:id`, replacing them with the corresponding values in the map """ - def update(%{"id" => id} = update_map) do - Repo.update(changeset(get(id), update_map)) + def update(%{"handle" => handle} = update_map) do + Repo.update(changeset(get(handle), update_map)) end end diff --git a/lib/hostas_web/controllers/denizen_controller.ex b/lib/hostas_web/controllers/denizen_controller.ex index 8b8bb09..611a7b8 100644 --- a/lib/hostas_web/controllers/denizen_controller.ex +++ b/lib/hostas_web/controllers/denizen_controller.ex @@ -6,11 +6,11 @@ defmodule HostasWeb.DenizenController do # alias Hostas.Denizen # alias Hostas.Token - def open(conn, _params) do + def register(conn, _params) do unimplemented(conn) end - def close(conn, _params) do + def deactivate(conn, _params) do unimplemented(conn) end diff --git a/lib/hostas_web/router.ex b/lib/hostas_web/router.ex index 84be245..c37bdfe 100644 --- a/lib/hostas_web/router.ex +++ b/lib/hostas_web/router.ex @@ -36,8 +36,8 @@ defmodule HostasWeb.Router do delete "/auth/token/:id", Auth.TokenController, :revoke get "/auth/token/:id/renew", Auth.TokenController, :renew - post "/denizen", DenizenController, :open - delete "/denizen/:id", DenizenController, :close + post "/denizen", DenizenController, :register + delete "/denizen/:id", DenizenController, :remove get "/denizen/:id", DenizenController, :show patch "/denizen/:id", DenizenController, :update diff --git a/test/hostas_web/controllers/denizen_controller_test.exs b/test/hostas_web/controllers/denizen_controller_test.exs new file mode 100644 index 0000000..b31fa5c --- /dev/null +++ b/test/hostas_web/controllers/denizen_controller_test.exs @@ -0,0 +1,110 @@ +defmodule HostasWeb.Auth.DenizenControllerTest do + use HostasWeb.ConnCase + + # For testing with Ecto + import Ecto.Query, only: [from: 2] + alias Hostas.Repo + + alias Hostas.Denizen + alias Hostas.Token + + @denizen %{ + "handle" => "test", + "name" => "Test Denizen", + "password" => "password", + } + + setup_all %{conn: conn} do + denizen1 = %Denizen{} + |> Denizen.changeset(%{handle: "denizen1", name: "Test Denizen 1", password: "password"}) + |> Repo.insert! + + denizen1 = Denizen.create(%{handle: "denizen1", name: "Test Denizen 1", password: "password"}) + denizen2 = Denizen.create(%{handle: "denizen2", name: "Test Denizen 2", password: "password"}) + + {:ok, token} = Token.new(denizen1.id) + + # Facilitate requests to protected routes + conn = put_req_header(conn, "authorization", "Bearer #{struct.token}") + + %{denizen1: denizen1, denizen2: denizen2, token: token, conn: conn} + end + + describe "register denizen" do + test "succeeds", %{conn: conn} do + conn = post(conn, ~p"/hostapi/denizen/", @denizen) + + assert json_response(conn, 201)["handle"] == @denizen["handle"] + assert json_response(conn, 201)["name"] == @denizen["name"] + assert json_response(conn, 201)["id"] == @denizen["id"] + end + + test "fails because duplicate handle", %{conn: conn, denizen1: pre_existing_denizen} do + pre_existing_handle = pre_existing_denizen["handle"] + conn = post( + conn, + ~p"/hostapi/denizen/", + Map.put(@denizen, "handle", pre_existing_handle) + ) + + assert json_response(conn, 422)["error"] == + "Denizen with handle #{pre_existing_handle} already exists" + end + end + + describe "show denizen" do + test "succeeds", %{conn: conn, denizen1: denizen} do + %{handle: handle} = denizen + conn = get(conn, ~p"/hostapi/denizen/#{handle}") + assert json_response(conn, 200) == Map.delete(denizen, :password) + end + + test "fails because handle is unknown", %{conn: conn} do + conn = get(conn, ~p"/hostapi/denizen/unknown_denizen") + assert json_response(conn, 404)["error"] == "No denizen corresponding to given handle" + end + end + + describe "deactivate denizen" do + test "returns 201 when done by same denizen", %{conn: conn, denizen1: denizen} do + %{handle: handle} = denizen + conn = delete(conn, ~p"/hostapi/denizen/#{handle}") + assert json_response(conn, 201) + end + + test "succeeds", %{conn: conn, denizen1: denizen} do + %{handle: handle} = denizen + conn = delete(conn, ~p"/hostapi/denizen/#{handle}") + assert json_response(conn, 201) + end + + test "fails because denizens can't deactivate one another", %{conn: conn, denizen2: denizen2} do + conn = delete(conn, ~p"/hostapi/denizen/#{denizen2.handle}") + assert json_response(conn, 403)["error"] == "Regular denizens cannot deactivate other denizens" + end + + test "fails because referenced denizen doesn't exist", %{conn: conn, denizen1: denizen} do + conn = delete(conn, ~p"/hostapi/denizen/unknown_denizen") + assert json_response(conn, 403)["error"] == "Regular denizens cannot deactivate other denizens" + end + end + + describe "update denizen" do + test "succeeds", %{conn: conn, denizen1: denizen} do + update = Map.put(denizen, :name, "Updated Name") + conn = patch(conn, ~p"/hostapi/denizen/#{denizen.handle}", update) + + assert json_response(conn, 200) == update + end + + test "fails because denizen can't modify someone else's account", %{conn: conn, denizen2: denizen2} do + conn = patch(conn, ~p"/hostapi/denizen/#{denizen.handle}", %{}) + assert json_response(conn, 403)["error"] == "Regular denizens cannot update other denizens" + end + + test "fails because denizen doesn't exist", %{conn: conn} do + conn = patch(conn, ~p"/hostapi/denizen/unknown_denizen", %{}) + assert json_response(conn, 403)["error"] == "Regular denizens cannot update other denizens" + end + end +end