Programming phoenix 1.4 chapter 8 page 151

Hi everyone,

I don’t understand why this test is failing instead of going to the changeset and give an error and pass the test. Because that error was expected in that test.

Expected result:

To append an error to the changeset and pass the tests for username to long and password to short

Test parts that fail because the password and users don’t match

test "does not accept long usernames" do
      attrs = Map.put(@valid_attrs, :username, String.duplicate("a", 30))
      {:error, changeset} = Accounts.register_user(attrs)

      assert %{username: ["should be at most 20 character(s)"]} = errors_on(changeset)
      assert Accounts.list_users() == []
    end

    test "requires password to be at least 6 chars long" do
      attrs = Map.put(@valid_attrs, :password, "12345")
      {:error, changeset} = Accounts.register_user(attrs)

      assert %{password: ["should be at least 6 character(s)"]} = errors_on(changeset)
      assert Accounts.list_users() == []
    end

Error trace:

rumbl mix test test/rumbl/accounts_test.exs

.

  1) test register_user/1 does not accept long usernames (Rumbl.AccountsTest)
     test/rumbl/accounts_test.exs:36
     ** (MatchError) no match of right hand side value: {:ok, %Rumbl.Accounts.User{__meta__: 
#Ecto.Schema.Metadata<:loaded, "users">, id: 18, inserted_at: ~N[2020-01-19 15:34:04], 
name: "User", password: "secret", password_hash: "$pbkdf2-sha512$160000$iahAZDIoM/oP/PA9AlAIXQ$mxd9aqd.QhF9MPPjhSOyu.QiMFIAdZkLUUXoRTbwpyoqB0V5af6FpfE/USCoAxMvC.HW1i1m.0B7o1JYHDAyDQ", updated_at: ~N[2020-01-19 15:34:04], username: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}}
     code: {:error, changeset} = Accounts.register_user(attrs)
     stacktrace:
       test/rumbl/accounts_test.exs:38: (test)



  2) test register_user/1 requires password to be at least 6 chars long (Rumbl.AccountsTest)
     test/rumbl/accounts_test.exs:44
     ** (MatchError) no match of right hand side value: {:ok, %Rumbl.Accounts.User{__meta__:
 #Ecto.Schema.Metadata<:loaded, "users">, id: 19, inserted_at: ~N[2020-01-19 15:34:05], 
name: "User", password: "12345", 
password_hash: 
"$pbkdf2-sha512$160000$g5239LoShgVmsdO7qvYzVQ$cJnOY1lKgDjsAYT7Svi2P58qZly0khxmhEwOMfYk6mELoOtxlKUe4YMC0AE8vf1.m7J0M8EdlQOsCly0hyGy7g", 
updated_at: ~N[2020-01-19 15:34:05], username: "eva"}}
     code: {:error, changeset} = Accounts.register_user(attrs)
     stacktrace:
       test/rumbl/accounts_test.exs:46: (test)

..

Finished in 5.5 seconds
5 tests, 2 failures

Randomized with seed 829346

Also the full test can be seen here

http://media.pragprog.com/titles/phoenix14/code/testing_mvc/listings/rumbl/test/rumbl/accounts_test.exs

Thanks in advance

You have told us how the test looks like, but not the function under test, perhaps even the validations mihgt be of special interest here.

Also, if that book does it in a TDD manor, have you peeked at the next couple of pages if they do make the tests pass on the next couple of pages?

The book doesn’t do the tests in TDD so the accounts context and user changeset can be of interest here?

The tests are testing if names of certain lengths are invalid.

As the tests are currently failing because of a MatchError where you call the Accounts.register_user/1 with {:error, _} not matching some {:ok, _}, it is very likely that the length validations for the username are currently either implemented wrong or missing at all.

Here is the registration_changeset

def registration_changeset(user, params) do
    user
    |> changeset(params)
    |> cast(params, [:password])
    |> validate_required([:password])
    |> validate_length(:password, min: 5, max: 100)
    |> put_pass_hash()
  end

Okay, take a look at your changeset. It does not care for the name at all.

Also somewhere inbetween, there seems to happen something else, as the resulting struct has name: "User" in both cases, while you pass in your wrong name as :username.

1 Like

Let me share the whole schema because the schema has also name and username in it one sec.

defmodule Rumbl.Accounts.User do
  use Ecto.Schema
  import Ecto.Changeset

  schema "users" do
    field :name, :string
    field :username, :string
    field :password, :string, virtual: true
    field :password_hash, :string

    timestamps()
  end

  def changeset(user, attrs) do
    user
    |> cast(attrs, [:name, :username])
    |> validate_required([:name, :username])
    |> validate_length(:username, min: 1, max: 75)
    |> unique_constraint(:username)
  end

  def registration_changeset(user, params) do
    user
    |> changeset(params)
    |> cast(params, [:password])
    |> validate_required([:password])
    |> validate_length(:password, min: 5, max: 100)
    |> put_pass_hash()
  end

  defp put_pass_hash(changeset) do
    case changeset do
      %Ecto.Changeset{valid?: true, changes: %{password: pass}} ->
        put_change(changeset, :password_hash, Pbkdf2.hash_pwd_salt(pass))

      _ ->
        changeset
    end
  end
end

Also the also the attrs are:

@valid_attrs %{
      name: "User",
      username: "eva",
      password: "secret"
    }
    @invalid_attrs %{}

Yes, now I see it. In both cases :username is set in the result. I just didn’t realize it in the mobile…

In the registration_changeset/1 you do not have any verification for the :username, its only in the changeset. Which one is called in the register_user/1?

1 Like

Like this:

file: Account context

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

  def change_registration(%User{} = user, params) do
    User.registration_changeset(user, params)
  end

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

I think that the registration changeset gets all the changeset values form the default chnageset and adds only passoword and password_hash to register_changeset.

Also you are right I had a validate_length of 75 for username, changed it to 20.

Solved one error the username now passes the test

I think the problem here is much simpler.

In the test you write

but in the actual validation only 5 characters are required.

This is why password: "12345" returns {:ok, _} instead of an error.

Changing the validation to min: 6 should solve the problem :slight_smile:

1 Like

Thanks @archdragon.

That was it I haven’t verified the changeset validation so my assertion couldn’t match.

These lines were the problems
Replace this

|> validate_length(:username, min: 1, max: 75)

with this

|> validate_length(:username, min: 1, max: 20)`

and this

|> validate_length(:password, min: 5, max: 100)

with this

|> validate_length(:password, min: 6, max: 20)

Now all tests are green and pass.

Thanks everyone for helping out

1 Like

2 posts were split to a new topic: User auth tables managed separately or all in one table?