Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reset password - test fails issue #679

Open
mateuszbabski opened this issue Oct 29, 2022 · 1 comment
Open

Reset password - test fails issue #679

mateuszbabski opened this issue Oct 29, 2022 · 1 comment

Comments

@mateuszbabski
Copy link

mateuszbabski commented Oct 29, 2022

Hi I really appreciate your work on Pow Auth. It is really great and compact. For learning purposes I try to create my custom controllers for the most important cases. Just to know how it works under the hood, to test it easily in Postman (putting tokens in json and pasting them in another bodies) and how to write good tests in Elixir.

I've struggling for many hours with testing reset password controller, it raises errors every time and that forced me to write an issue cause my digging through web leads to nowhere :)

I start from beginning. Here is my forgot_password func. Pretty straight forward except token in json - easy to test with postman.

@spec forgot_password(Conn.t(), map()) :: Conn.t()
  def forgot_password(conn, email) do
    conn
    |> PowResetPassword.Plug.create_reset_token(email)
    |> case do
      {:ok, %{token: token, user: _user}, conn} ->
        #send email to be implemented
        conn
        |> put_status(200)
        |> json(%{data: %{status: 200, message: "Email has been sent", token: token}})

      {:error, _changeset, conn} ->
        conn
        |> put_status(200)
        |> json(%{data: %{status: 200, message: "Email has been sent"}})
    end
  end

And here is my reset_password controller:

@spec reset_password(Conn.t(), map()) :: Conn.t()
  def reset_password(conn, %{"id" => token, "user" => user_params}) do
    with {:ok, conn} <- PowResetPassword.Plug.load_user_by_token(conn, token),
         {:ok, _user, conn}  <- PowResetPassword.Plug.update_user_password(conn, user_params) do
          json(conn, %{status: "Password changed"})
    else
          {:error, _changeset, conn} ->
            json(conn, %{error: %{message: "Passwords are not the same"}})

          _ ->
            json(conn, %{error: %{message: "Expired Token"}})
    end
  end

Everything works fine during testing in Postman - I dont know yet how to use session/cache token in this application so I use what I learnt while learning C# with JWT tokes.

I put my forgot_password and reset_password tests in one file (the same like they both are in one controller file) Here is my forgot_password part:

setup do
    user =
      %User{}
        |> User.changeset(%{email: "[email protected]", password: @password, password_confirmation: @password})
        |> Repo.insert!()

    {:ok, user: user}
  end

  describe "forgot_password/2" do

    test "with email in database", %{conn: conn} do
      conn = post(conn, Routes.password_path(conn, :forgot_password, @valid_email))

      assert json = json_response(conn, 200)
      assert json["data"]["status"]
      assert json["data"]["message"]
      assert json["data"]["token"]
    end

    test "with invalid email", %{conn: conn} do
      conn = post(conn, Routes.password_path(conn, :forgot_password, @invalid_email))

      assert json = json_response(conn, 200)
      assert json["data"]["status"]
      assert json["data"]["message"]
    end
  end

It works well. Simple, compact, 5 minutes of work. Then problems started to appear - I've tried many configurations of reset password tests but nothing seems to work.

1st attemp:

@valid_params %{"id" => "token", "user" => %{"password" => @new_password, "password_confirmation" => @new_password}}

  describe "reset_password/2" do

    test "with valid token and passwords", %{conn: conn} do
      conn = post(conn, Routes.password_path(conn, :reset_password, @valid_params))

      assert json = json_response(conn, 200)
      assert json["status"]
    end
  end

Witth error:

Expected truthy, got nil
     code: assert json["status"]
     arguments:

         # 1
         %{"error" => %{"message" => "Expired Token"}}

         # 2
         "status"

I suppose it needs real token. So pretty simple that It crashed.

2nd attemp:

use PowApiTemplateWeb.ConnCase

  alias Plug.Conn
  alias PowApiTemplate.{Repo, Users.User}
  alias PowResetPassword.Plug
  alias Pow.Plug, as: PowPlug

setup do
    user =
      %User{}
        |> User.changeset(%{email: "[email protected]", password: @password, password_confirmation: @password})
        |> Repo.insert!()

    {:ok, user: user}
  end

  describe "reset_password/2" do
    setup %{conn: conn} do
      token = PowResetPassword.Plug.create_reset_token(conn, "[email protected]")

      {:ok, conn: conn, token: token}
    end

    test "with valid token and passwords", %{conn: conn} do
      conn = post(conn, Routes.password_path(conn, :reset_password, %{"id" => token, "user" => %{"password" => @new_password, "password_confirmation" => @new_password}}))

      assert json = json_response(conn, 200)
      assert json["status"]
    end
  end

At first I thought it has to be working. Token is invoked and I didnt use any variables to omit mistakes. Then I got error:
(Pow.Config.ConfigError) Pow configuration not found in connection. Please use a Pow plug that puts the Pow configuration in the plug connection.

I have no idea what to do now. I spent many hours on elixir forum, with documentation and on github trying to find people with similar problem or repos with custom controllers but it leads to nowhere. I have in my head another solutions, but I know too little yet to do it all by myself without any tips. I dont want to go further without understanding this part and I want to have my own boilerplate for my projects.

I have also some questions:

  1. What comes to my mind is that maybe I need to mock some part of test. Dont know yet which.
  2. I dont know If that type of controllers are possible/secure to go in production. I know that whole Pow is about caching tokes and invoking next functions then. If I use them like I did - they are needed to be pasted into params by "hand" - are they still also passed under the hood in cache? For now I learn mostly API/Backend stuff and it's easy to test it in Postman in contrast to playing with cache/session tokes, but If it is terrible way I just need to know and learn it in other way (this is how 99% courses for C# are taught).

Greetings and thanks for your help :)

@mateuszbabski
Copy link
Author

mateuszbabski commented Oct 30, 2022

Update:

I think I made big step with this case. I digged into issues here and I found something that looks promising to me. After all it still shows an error but I think it's pretty easy to pass, but still I didnt found solution.

I added to test folder Ets Cache Mock:

defmodule PowApiTemplate.Test.EtsCacheMock do
  @moduledoc false
  @tab __MODULE__

  def init, do: :ets.new(@tab, [:ordered_set, :protected, :named_table])

  def get(config, key) do
    ets_key = ets_key(config, key)

    @tab
    |> :ets.lookup(ets_key)
    |> case do
      [{^ets_key, value} | _rest] -> value
      []                          -> :not_found
    end
  end

  def delete(config, key) do
    :ets.delete(@tab, ets_key(config, key))

    :ok
  end

  def put(config, record_or_records) do
    records     = List.wrap(record_or_records)
    ets_records = Enum.map(records, fn {key, value} ->
      {ets_key(config, key), value}
    end)

    send(self(), {:ets, :put, records, config})
    :ets.insert(@tab, ets_records)
  end

  def all(config, match) do
    ets_key_match = ets_key(config, match)

    @tab
    |> :ets.select([{{ets_key_match, :_}, [], [:"$_"]}])
    |> Enum.map(fn {[_namespace | keys], value} -> {keys, value} end)
  end

  defp ets_key(config, key) do
    [Keyword.get(config, :namespace, "cache")] ++ List.wrap(key)
  end
end

Updated test config with:

config :pow_api_template, :pow,
  cache_backend: [cache_store_backend: PowApiTemplate.Test.EtsCacheMock]

Error is the same if I change it to:

config :pow_api_template, :pow,
  cache_store_backend: PowApiTemplate.Test.EtsCacheMock

I updated conn_case.ex with:

  setup _tags do
    EtsCacheMock.init()

    {:ok, conn: Phoenix.ConnTest.build_conn(), ets: EtsCacheMock}
  end

Adding this line to test_helper.exs leads to fail all tests - even with registration/session controllers:

PowApiTemplate.Test.EtsCacheMock.init()

I also updated password_controller_test.exs:

 setup do
    user =
      %User{}
        |> User.changeset(%{email: "[email protected]", password: @password, password_confirmation: @password})
        |> Repo.insert!()

    {:ok, user: user}
  end

describe "reset_password/2" do

    test "with valid token and passwords", %{conn: conn} do
      PowApiTemplate.Test.EtsCacheMock.init()
      pow_config = [otp_app: :pow_api_template]

      {:ok, %{token: token, user: user}, conn} =
        conn
        |> Pow.Plug.put_config(Application.get_env(:pow_api_template, :pow))
        |> PowResetPassword.Plug.create_reset_token(%{"email" => "[email protected]"})

      valid_params = %{"id" => token, "user" => %{"password" => @new_password, "password_confirmation" => @new_password}}

      conn = post(conn, Routes.password_path(conn, :reset_password, valid_params))

      assert json = json_response(conn, 200)
    end
  end

Trying to start test leads to error:

1) test reset_password/2 with valid token and passwords (PowApiTemplateWeb.PasswordControllerTest)
    test/pow_api_template_web/controllers/password_controller_test.exs:45
    ** (ArgumentError) errors were found at the given arguments:
    
      * 2nd argument: invalid options
    
    code: PowApiTemplate.Test.EtsCacheMock.init()
    stacktrace:
      (stdlib 4.0.1) :ets.new(PowApiTemplate.Test.EtsCacheMock, [:set, :protected, :named_table])
      (pow_api_template 0.1.0) test/support/ets_cache_mock.ex:5: PowApiTemplate.Test.EtsCacheMock.init/0
      test/pow_api_template_web/controllers/password_controller_test.exs:46: (test)

I also changed :set to :ordered_set but nothing changes with error. Maybe in Pow 1.0.27 there are changes that I didnt implemented or I wrote something incorrectly. I'll check later PowApiTemplate.Test.EtsCacheMock.init() function and I will try to play with options.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant