mbski

mbski

Testing controllers - unexpected problems with custom Pow auth

Hi, during creating my own custom Pow auth I stuck during testing. I made a research on web but couldnt find solution.

For learning purpose I show token in json. I need it to paste in postman during manual testing (i dont know yet how to use postman with session/cookies auth :frowning: )

@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

Function works well, shows expected errors while providing invalid params or token. I wanted to write tests for it and then I stuck. I cant go through them and my every try is failed.

I setup up user before tests:

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

    {:ok, user: user}
  end

First 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

Error in terminal:

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

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

         # 2
         "status"

I suppose that it needs real token, not any hard coded pretending to be real.

Second attemp:

  use PowApiTemplateWeb.ConnCase

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

  describe "reset_password/2" do
    setup %{conn: conn} do
      token = PowResetPassword.Plug.create_reset_token(conn, "test@example.com")

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

    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

It seems to be working. I use real conn, use function providing token, but there appears an error i couldn’t resolve even if I checked for solutions, check if I write in config everything like it is written in docs:

(Pow.Config.ConfigError) Pow configuration not found in connection. Please use a Pow plug that puts the Pow configuration in the plug connection.

I tried also different way of writing this func but Pow config errors still appears. I dont think my function in untestable, but there are issues I can resolve after digging in docs:

  describe "reset_password/2" do

    test "with valid token and passwords", %{conn: conn} do
      token = PowResetPassword.Plug.create_reset_token(conn, "test@example.com")
      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

Thanks for any help :slight_smile:

First Post!

mbski

mbski

I made quite big step further. Still I have errors but with ETS.

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: "test@example.com", 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" => "test@example.com"})

      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.

Where Next?

Popular in Questions Top

Tee
can someone please explain to me how Enum.reduce works with maps
New
itssasanka
Hi all, Trying to get some more clarity over utc_datetime and naive_datetime for Ecto: https://hexdocs.pm/ecto/Ecto.Schema.html#module-...
New
jononomo
I am trying to figure out how Mix knows whether the environment is test, dev, or prod -- where is this set? Thanks.
New
Kagamiiiii
Student &amp; New to elixir. Nice language. I want to convert a english character, e.g. “a”, which is stored in a variable, to it’s asci...
New
vertexbuffer
Hello, can anybody help here..? I have a list of players and I what to delete an element, but every for loop the list is reverting to ori...
New
lastday4you
I wanted to check elixir version in phoenix because i found that my elixir is 1.5 but when i use Enum.chunk_by it said the function is un...
New
stefanchrobot
What’s the safe way to decode a JSON string into a struct? I want to avoid calling String.to_atom. Jason.decode can give me a map with st...
New
aadeshere1
I have a another noob question about loop. Since elixir is immutable, while loop is not directly possible. total = 10 while total != 0 ...
New
johnnyicon
Hi all, I've just started learning Elixir and Phoenix Framework, so please pardon my n00bness at this stage. I'm trying to use Postg...
New
Exadra37
Sometimes I want to check if the input into a function is not a blank string. My first approach: defmodule Example do def do_stuff(s...
New

Other popular topics Top

vonH
In asking this question I am more interested about the expressiveness of the language itself and less concerned about the availability of...
New
gshaw
What is the idiomatic way of matching for not nil in Elixir? E.g., First way: defp halt_if_not_signed_in(conn, signed_in_account) when...
New
malloryerik
Hi, this is for people who, like me, have had some friction using .html.heex templates in VSCode. The solution seems to be, in a hyphena...
New
KronicDeth
Elixir plugin for JetBrain’s IntelliJ Platform (including Rubymine) This is a plugin that adds support for Elixir to JetBrains IntelliJ...
289 35421 110
New
Harrisonl
We have an ECS cluster with 4 services, where each task joins a single cluster, via discovery ECS discovery service. Currently when I de...
New
jononomo
I am trying to figure out how Mix knows whether the environment is test, dev, or prod -- where is this set? Thanks.
New
fireproofsocks
I’m working on defining a simple Ecto schema for a table (in PostGres), but I don’t see where I can define a column as NOT NULL. Conside...
New
mgjohns61585
Could someone help me? I'm making my first elixir program, number guessing game. I can't figure out how to convert the user's guess from ...
New
ashish173
I am using Ecto timestamps with postgres, I can see the timestamps() use the :naive_dateime but for my use case I wanted to store the ti...
New
baxterw3b
Hi guys, i’m new in the Elixir world, and i have to say, that i love it! i’m having some problem to understand anonymous functions with ...
New

We're in Beta

About us Mission Statement