POW library testing

Hello,

I am trying to setup test for a simple crud application. I am also utilizing the POW library. I am running into a problem when I am trying to test my update_user/2 function.

accounts.ex

defmodule Profilo.Accounts do
  import Ecto.Query, warn: false

  alias Profilo.Repo
  alias Profilo.Accounts.Lib.User
  alias Bcrypt

  def list_users do
    Repo.all(User)
  end

  def get_user!(id) do
    User
    |> Repo.get!(id)
  end

  def create_user(attrs \\ %{}) do
    %User{}
    |> User.changeset(attrs)
    |> Repo.insert()
  end

  def update_user(%User{} = user, attrs) do
    user
    |> User.changeset(attrs)
    |> Repo.update()
  end

  def change_user(%User{} = user) do
    user
    |> User.changeset(%{})
  end

  def delete_user(%User{} = user) do
    user
    |> Repo.delete()
  end
end

accounts_test.exs:

describe "users" do
    alias Profilo.Accounts.Lib.User

    @valid_attrs %{email: "test@gmail.com", password: "1234567890", current_password: "1234567890", confirm_password: "1234567890", address: "some address", company: "some company", first_name: "some first_name", last_name: "some last_name", phone_number: "some phone_number", website: "some website"}
    @update_attrs %{email: "test@gmail.com", address: "some updated address", company: "some updated company", first_name: "some updated first_name", last_name: "some updated last_name", phone_number: "some updated phone_number", website: "some updated website"}
    @invalid_attrs %{address: nil, company: nil, first_name: nil, last_name: nil, phone_number: nil, website: nil, email: nil, password_hash: nil}

    def user_fixture(attrs \\ %{}) do
      {:ok, user} =
        attrs
        |> Enum.into(@valid_attrs)
        |> Accounts.create_user()

      user
    end

    def update_password(%User{} = user, %{confirm_password: confirm_password, current_password: current_password, password: password}) do
      user
        |> Map.put(:confirm_password, confirm_password)
        |> Map.put(:current_password, current_password)
        |> Map.put(:password, password)
    end

    test "list_users/0 returns all users" do
      user = user_fixture() |> update_password(%{current_password: nil, confirm_password: nil, password: nil})

      assert Accounts.list_users() == [user]
    end

    test "get_user!/1 returns the user with given id" do
      user = user_fixture() |> update_password(%{current_password: nil, confirm_password: nil, password: nil})

      assert Accounts.get_user!(user.id) == user
    end

    test "create_user/1 with valid data creates a user" do
      assert {:ok, %User{} = user} = Accounts.create_user(@valid_attrs)
      assert user.email == "test@gmail.com"
      assert user.address == "some address"
      assert user.company == "some company"
      assert user.first_name == "some first_name"
      assert user.last_name == "some last_name"
      assert user.phone_number == "some phone_number"
      assert user.website == "some website"
    end

    test "create_user/1 with invalid data returns error changeset" do
      assert {:error, %Ecto.Changeset{}} = Accounts.create_user(@invalid_attrs)
    end

    test "update_user/2 with valid data updates the user" do
      user = user_fixture()
      assert {:ok, %User{} = user} = Accounts.update_user(user, @update_attrs)
      assert user.address == "some updated address"
      assert user.company == "some updated company"
      assert user.first_name == "some updated first_name"
      assert user.last_name == "some updated last_name"
      assert user.phone_number == "some updated phone_number"
      assert user.website == "some updated website"
    end

    test "update_user/2 with invalid data returns error changeset" do
      user = user_fixture()
      assert {:error, %Ecto.Changeset{}} = Accounts.update_user(user, @invalid_attrs)
      assert user == Accounts.get_user!(user.id)
    end

    test "delete_user/1 deletes the user" do
      user = user_fixture()
      assert {:ok, %User{}} = Accounts.delete_user(user)
      assert_raise Ecto.NoResultsError, fn -> Accounts.get_user!(user.id) end
    end

    test "change_user/1 returns a user changeset" do
      user = user_fixture()
      assert %Ecto.Changeset{} = Accounts.change_user(user)
    end
  end
end

user.ex

defmodule Profilo.Accounts.Lib.User do
  use Ecto.Schema
  use Pow.Ecto.Schema
  use PowAssent.Ecto.Schema

  import Ecto.Changeset

  alias Bcrypt

  schema "users" do
    has_many :user_identities,
      Profilo.Accounts.Lib.UserIdentity,
      on_delete: :delete_all,
      foreign_key: :user_id

    field :first_name, :string
    field :last_name, :string
    field :address, :string
    field :company, :string
    field :phone_number, :string
    field :website, :string
    field :is_admin, :boolean, default: false

    pow_user_fields()

    timestamps()
  end

  @required_fields ~w(first_name last_name)a
  @optional_fields ~w(address company phone_number website)a

  def changeset(user, attrs) do
    user
    |> pow_changeset(attrs)
    |> cast(attrs, @required_fields ++ @optional_fields)
    |> validate_required(@required_fields)
  end
end

What i think the problem is:

  • I feel like I should test POW update separately from my update function. As seen from the error below, Pow.Ecto.Schema.Changeset.validate_current_password/3 requiires 3 arguments but when I call my update_user/2 I can only pass in 2 arguments. I am unclear as to how i should approach this

There error I am getting:


  1) test users change_user/1 returns a user changeset (Profilo.AccountsTest)
     test/profilo/accounts/accounts_test.exs:79
     ** (FunctionClauseError) no function clause matching in Pow.Ecto.Schema.Changeset.validate_current_password/2

     The following arguments were given to Pow.Ecto.Schema.Changeset.validate_current_password/2:
     
         # 1
         #Ecto.Changeset<action: nil, changes: %{}, errors: [], data: #Profilo.Accounts.Lib.User<>, valid?: true>
     
         # 2
         []
     
     Attempted function clauses (showing 1 out of 1):
     
         defp validate_current_password(%{data: user, changes: %{current_password: password}} = changeset, config)
     
     code: assert %Ecto.Changeset{} = Accounts.change_user(user)
     stacktrace:
       (pow) lib/pow/ecto/schema/changeset.ex:124: Pow.Ecto.Schema.Changeset.validate_current_password/2
       (profilo) lib/profilo/accounts/lib/user.ex:3: Profilo.Accounts.Lib.User.pow_changeset/2
       (profilo) lib/profilo/accounts/lib/user.ex:34: Profilo.Accounts.Lib.User.changeset/2
       test/profilo/accounts/accounts_test.exs:81: (test)

It’s because the user struct has a polluted :current_password virtual field. Because of that, the :current_password change is not registered since it’s identical to the struct field (or blank), however the validation goes through since it’ll check both struct and changeset changes for the :current_password field (and thus validate_current_password/2 will be called to verify password).

I’m working on a bug fix right now. To fix it with the current version of Pow, you can just reset the virtual field:

    def user_fixture(attrs \\ %{}) do
      {:ok, user} =
        attrs
        |> Enum.into(@valid_attrs)
        |> Accounts.create_user()

      %{user | current_password: nil}
    end
4 Likes

Bug fix merged to master: https://github.com/danschultzer/pow/pull/177

Thanks for letting me know!

5 Likes

hey @danschultzer I tried what you suggested; however, I am getting the following error:

 3) test users update_user/2 with valid data updates the user (Profilo.AccountsTest)
     test/profilo/accounts/accounts_test.exs:56
     match (=) failed
     code:  assert {:ok, %User{} = user} = Accounts.update_user(user, @update_attrs)
     right: {:error,
             #Ecto.Changeset<
               action: :update,
               changes: %{
                 address: "some updated address",
                 company: "some updated company",
                 first_name: "some updated first_name",
                 last_name: "some updated last_name",
                 phone_number: "some updated phone_number",
                 website: "some updated website"
               },
               errors: [
                 current_password: {"can't be blank", [validation: :required]}
               ],
               data: #Profilo.Accounts.Lib.User<>,
               valid?: false
             >}
     stacktrace:
       test/profilo/accounts/accounts_test.exs:59: (test)

Also, how can I get the updated source code? I tried running mix deps.update --all; however, I’m still not seeing these changes.

That’s expected behavior since :current_password is required for updating the user.

You just have to add :current_password to your @update_attrs:

@update_attrs %{email: "test@gmail.com", address: "some updated address", company: "some updated company", first_name: "some updated first_name", last_name: "some updated last_name", phone_number: "some updated phone_number", website: "some updated website", current_password: "1234567890"}

If you wish to use the master branch from github then you have to update deps like so: {:pow, github: "danschultzer/pow"}. It would need override: true option too if you’re using PowAssent. However you would get the same error as above since current password is required.

1 Like

Hey @danschultzer I really appreciate all the help. But i am still running to the same issue:

accounts_test.exs

defmodule Profilo.AccountsTest do
  use Profilo.DataCase

  alias Profilo.Accounts

  describe "users" do
    alias Profilo.Accounts.Lib.User

    @valid_attrs %{email: "test@gmail.com", password: "1234567890", current_password: "1234567890", confirm_password: "1234567890", address: "some address", company: "some company", first_name: "some first_name", last_name: "some last_name", phone_number: "some phone_number", website: "some website"}
    @update_attrs %{email: "test@gmail.com", current_password: "1234567890", address: "some updated address", company: "some updated company", first_name: "some updated first_name", last_name: "some updated last_name", phone_number: "some updated phone_number", website: "some updated website"}
    @invalid_attrs %{address: nil, company: nil, first_name: nil, last_name: nil, phone_number: nil, website: nil, email: nil, password_hash: nil}

    def user_fixture(attrs \\ %{}) do
      {:ok, user} =
        attrs
        |> Enum.into(@valid_attrs)
        |> Accounts.create_user()

      user
    end

    def update_password(%User{} = user, %{confirm_password: confirm_password, current_password: current_password, password: password}) do
      user
        |> Map.put(:confirm_password, confirm_password)
        |> Map.put(:current_password, current_password)
        |> Map.put(:password, password)
    end

    test "list_users/0 returns all users" do
      user = user_fixture() |> update_password(%{current_password: nil, confirm_password: nil, password: nil})

      assert Accounts.list_users() == [user]
    end

    test "get_user!/1 returns the user with given id" do
      user = user_fixture() |> update_password(%{current_password: nil, confirm_password: nil, password: nil})

      assert Accounts.get_user!(user.id) == user
    end

    test "create_user/1 with valid data creates a user" do
      assert {:ok, %User{} = user} = Accounts.create_user(@valid_attrs)
      assert user.email == "test@gmail.com"
      assert user.address == "some address"
      assert user.company == "some company"
      assert user.first_name == "some first_name"
      assert user.last_name == "some last_name"
      assert user.phone_number == "some phone_number"
      assert user.website == "some website"
    end

    test "create_user/1 with invalid data returns error changeset" do
      assert {:error, %Ecto.Changeset{}} = Accounts.create_user(@invalid_attrs)
    end

    test "update_user/2 with valid data updates the user" do
      user = user_fixture()
      user = %{user | current_password: "1234567890"}
      assert {:ok, %User{} = user} = Accounts.update_user(user, @update_attrs)
      assert user.address == "some updated address"
      assert user.company == "some updated company"
      assert user.first_name == "some updated first_name"
      assert user.last_name == "some updated last_name"
      assert user.phone_number == "some updated phone_number"
      assert user.website == "some updated website"
    end

    test "update_user/2 with invalid data returns error changeset" do
      user = user_fixture()
      assert {:error, %Ecto.Changeset{}} = Accounts.update_user(user, @invalid_attrs)
      assert user == Accounts.get_user!(user.id)
    end

    test "delete_user/1 deletes the user" do
      user = user_fixture()
      assert {:ok, %User{}} = Accounts.delete_user(user)
      assert_raise Ecto.NoResultsError, fn -> Accounts.get_user!(user.id) end
    end

    test "change_user/1 returns a user changeset" do
      user = user_fixture()
      assert %Ecto.Changeset{} = Accounts.change_user(user)
    end
  end
end

Error:

8) test users update_user/2 with valid data updates the user (Profilo.AccountsTest)
     test/profilo/accounts/accounts_test.exs:56
     ** (FunctionClauseError) no function clause matching in Pow.Ecto.Schema.Changeset.validate_current_password/2

     The following arguments were given to Pow.Ecto.Schema.Changeset.validate_current_password/2:
     
         # 1
         #Ecto.Changeset<action: nil, changes: %{}, errors: [], data: #Profilo.Accounts.Lib.User<>, valid?: true>
     
         # 2
         []
     
     Attempted function clauses (showing 1 out of 1):
     
         defp validate_current_password(%{data: user, changes: %{current_password: password}} = changeset, config)
     
     code: assert {:ok, %User{} = user} = Accounts.update_user(user, @update_attrs)
     stacktrace:
       (pow) lib/pow/ecto/schema/changeset.ex:124: Pow.Ecto.Schema.Changeset.validate_current_password/2
       (profilo) lib/profilo/accounts/lib/user.ex:3: Profilo.Accounts.Lib.User.pow_changeset/2
       (profilo) lib/profilo/accounts/lib/user.ex:34: Profilo.Accounts.Lib.User.changeset/2
       (profilo) lib/profilo/accounts/accounts.ex:25: Profilo.Accounts.update_user/2
       test/profilo/accounts/accounts_test.exs:59: (test)

Did you update to use the master branch on github? It seems like you are still using the hex releases, so you have to reset the :current_password field in the user struct:

Thanks @danschultzer, I was able to resolve the issue.

Can you explain again as to why the issue happened in the first place. I wasn’t able to understand your initial explanation.

The reason it failed was because you were setting :current_password in the @valid_attrs params. It’s not required or even used when creating a user.

When updating a user it’s required. However the virtual :current_password field was already set when creating the user and exists in the struct. So when the changeset is created the :current_password change is ignored because the value is identical to the value in the struct.

From there the validation fails because Pow check the changes to validate the current password. It’s expected that the :current_password virtual field is always empty, but now I have added a bug fix to make sure it’s blank before casting the params.

Okay, that makes sense. Thanks for the clarification.