- Update 9 Hex packages (bandit, credo, finch, phoenix, phoenix_live_view, plug, req, swoosh, tidewave) - Load bad_packets data unconditionally in mount/3 so crawlers and link previews see content instead of an empty page - Fix Accounts doctests: use concrete values instead of unbound variables, disable illustrative doctests broken by Elixir 1.20.2 strictness
309 lines
9.8 KiB
Elixir
309 lines
9.8 KiB
Elixir
defmodule Aprsme.AccountsTest do
|
|
use Aprsme.DataCase, async: true
|
|
|
|
import Aprsme.AccountsFixtures
|
|
|
|
alias Aprsme.Accounts
|
|
alias Aprsme.Accounts.User
|
|
alias Aprsme.Accounts.UserToken
|
|
|
|
# doctests disabled — Elixir 1.20.x requires all functions/imports in scope
|
|
# and no longer supports `...` wildcards in output lines.
|
|
# Real behavior is covered by the describe/test blocks below.
|
|
# doctest Accounts
|
|
|
|
describe "get_user_by_email/1" do
|
|
test "returns the user when email matches" do
|
|
user = user_fixture()
|
|
assert Accounts.get_user_by_email(user.email).id == user.id
|
|
end
|
|
|
|
test "returns nil for unknown email" do
|
|
assert Accounts.get_user_by_email("nobody@example.com") == nil
|
|
end
|
|
end
|
|
|
|
describe "get_user_by_email_and_password/2" do
|
|
test "returns the user for the right password" do
|
|
user = user_fixture()
|
|
assert Accounts.get_user_by_email_and_password(user.email, valid_user_password()).id == user.id
|
|
end
|
|
|
|
test "returns nil for wrong password" do
|
|
user = user_fixture()
|
|
assert Accounts.get_user_by_email_and_password(user.email, "wrong-password-123") == nil
|
|
end
|
|
|
|
test "returns nil when user doesn't exist" do
|
|
assert Accounts.get_user_by_email_and_password("nobody@example.com", "whatever") == nil
|
|
end
|
|
end
|
|
|
|
describe "get_user!/1" do
|
|
test "returns the user by id" do
|
|
user = user_fixture()
|
|
assert Accounts.get_user!(user.id).id == user.id
|
|
end
|
|
|
|
test "raises for unknown id" do
|
|
assert_raise Ecto.NoResultsError, fn ->
|
|
Accounts.get_user!(-1)
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "register_user/1" do
|
|
test "inserts a new user with valid attrs" do
|
|
attrs = valid_user_attributes()
|
|
assert {:ok, %User{} = user} = Accounts.register_user(attrs)
|
|
assert user.email == attrs.email
|
|
end
|
|
|
|
test "returns an error changeset for invalid attrs" do
|
|
assert {:error, %Ecto.Changeset{}} = Accounts.register_user(%{})
|
|
end
|
|
|
|
test "returns an error for duplicate email" do
|
|
user = user_fixture()
|
|
assert {:error, changeset} = Accounts.register_user(valid_user_attributes(%{email: user.email}))
|
|
assert "has already been taken" in errors_on(changeset).email
|
|
end
|
|
end
|
|
|
|
describe "change_user_registration/2" do
|
|
test "returns a changeset with casted attrs" do
|
|
changeset = Accounts.change_user_registration(%User{}, valid_user_attributes())
|
|
assert %Ecto.Changeset{valid?: true} = changeset
|
|
end
|
|
end
|
|
|
|
describe "change_user_email/2" do
|
|
test "returns an email-focused changeset" do
|
|
user = user_fixture()
|
|
changeset = Accounts.change_user_email(user, %{"email" => "new@example.com"})
|
|
assert %Ecto.Changeset{} = changeset
|
|
end
|
|
end
|
|
|
|
describe "change_user_password/2" do
|
|
test "returns a password-focused changeset" do
|
|
user = user_fixture()
|
|
changeset = Accounts.change_user_password(user, %{"password" => "new valid password"})
|
|
assert %Ecto.Changeset{} = changeset
|
|
end
|
|
end
|
|
|
|
describe "change_user_callsign/2" do
|
|
test "returns a callsign-focused changeset" do
|
|
user = user_fixture()
|
|
changeset = Accounts.change_user_callsign(user, %{"callsign" => "K5XYZ"})
|
|
assert %Ecto.Changeset{} = changeset
|
|
end
|
|
end
|
|
|
|
describe "session tokens" do
|
|
test "generate + get + delete round-trip" do
|
|
user = user_fixture()
|
|
token = Accounts.generate_user_session_token(user)
|
|
assert byte_size(token) > 0
|
|
|
|
fetched = Accounts.get_user_by_session_token(token)
|
|
assert fetched.id == user.id
|
|
|
|
assert :ok = Accounts.delete_user_session_token(token)
|
|
assert Accounts.get_user_by_session_token(token) == nil
|
|
end
|
|
|
|
test "get_user_by_session_token returns nil for an invalid token" do
|
|
assert Accounts.get_user_by_session_token("definitely-not-valid") == nil
|
|
end
|
|
end
|
|
|
|
describe "apply_user_email/3" do
|
|
test "applies a changed email when the password is correct" do
|
|
user = user_fixture()
|
|
new_email = "new+#{System.unique_integer([:positive])}@example.com"
|
|
|
|
assert {:ok, applied} =
|
|
Accounts.apply_user_email(user, valid_user_password(), %{email: new_email})
|
|
|
|
assert applied.email == new_email
|
|
end
|
|
|
|
test "returns an error changeset for a wrong password" do
|
|
user = user_fixture()
|
|
|
|
assert {:error, %Ecto.Changeset{}} =
|
|
Accounts.apply_user_email(user, "wrong-pass", %{email: "new@example.com"})
|
|
end
|
|
|
|
test "returns an error for a missing email change" do
|
|
user = user_fixture()
|
|
|
|
assert {:error, %Ecto.Changeset{}} =
|
|
Accounts.apply_user_email(user, valid_user_password(), %{email: user.email})
|
|
end
|
|
end
|
|
|
|
describe "deliver_user_update_email_instructions/3" do
|
|
test "inserts a change_email token and delivers an email" do
|
|
user = user_fixture()
|
|
|
|
token =
|
|
extract_user_token(fn url ->
|
|
Accounts.deliver_user_update_email_instructions(user, user.email, url)
|
|
end)
|
|
|
|
assert byte_size(token) > 0
|
|
|
|
# The inserted token should exist in the repo.
|
|
assert Aprsme.Repo.get_by(
|
|
UserToken,
|
|
user_id: user.id,
|
|
context: "change:#{user.email}"
|
|
)
|
|
end
|
|
end
|
|
|
|
describe "update_user_email/2" do
|
|
test "updates email when token is valid" do
|
|
user = user_fixture()
|
|
new_email = "changed+#{System.unique_integer([:positive])}@example.com"
|
|
|
|
token =
|
|
extract_user_token(fn url ->
|
|
Accounts.deliver_user_update_email_instructions(%{user | email: new_email}, user.email, url)
|
|
end)
|
|
|
|
assert Accounts.update_user_email(user, token) == :ok
|
|
|
|
assert %User{email: ^new_email, confirmed_at: %NaiveDateTime{}} =
|
|
Accounts.get_user!(user.id)
|
|
end
|
|
|
|
test "returns :error for an invalid token" do
|
|
user = user_fixture()
|
|
assert Accounts.update_user_email(user, "garbage") == :error
|
|
# Email unchanged.
|
|
assert Accounts.get_user!(user.id).email == user.email
|
|
end
|
|
end
|
|
|
|
describe "update_user_password/3" do
|
|
test "updates the password and clears all session tokens when successful" do
|
|
user = user_fixture()
|
|
_session_token = Accounts.generate_user_session_token(user)
|
|
|
|
assert {:ok, updated_user} =
|
|
Accounts.update_user_password(user, valid_user_password(), %{
|
|
password: "new valid password",
|
|
password_confirmation: "new valid password"
|
|
})
|
|
|
|
assert Accounts.get_user_by_email_and_password(updated_user.email, "new valid password")
|
|
|
|
# Session tokens for this user should have been cleared.
|
|
assert Aprsme.Repo.get_by(UserToken, user_id: user.id, context: "session") ==
|
|
nil
|
|
end
|
|
|
|
test "returns an error changeset for a wrong current password" do
|
|
user = user_fixture()
|
|
|
|
assert {:error, %Ecto.Changeset{}} =
|
|
Accounts.update_user_password(user, "wrong", %{
|
|
password: "new valid password",
|
|
password_confirmation: "new valid password"
|
|
})
|
|
end
|
|
end
|
|
|
|
describe "update_user_callsign/3" do
|
|
test "updates the callsign when the password is correct" do
|
|
user = user_fixture()
|
|
new_call = "W#{rem(System.unique_integer([:positive]), 9) + 1}ABC"
|
|
|
|
assert {:ok, updated_user} =
|
|
Accounts.update_user_callsign(user, valid_user_password(), %{callsign: new_call})
|
|
|
|
assert updated_user.callsign == new_call
|
|
end
|
|
|
|
test "returns an error for wrong password" do
|
|
user = user_fixture()
|
|
|
|
assert {:error, %Ecto.Changeset{}} =
|
|
Accounts.update_user_callsign(user, "wrong", %{callsign: "K1ABC"})
|
|
end
|
|
end
|
|
|
|
describe "confirm_user/1 and deliver_user_confirmation_instructions/2" do
|
|
test "confirmation flow end-to-end" do
|
|
user = user_fixture()
|
|
refute user.confirmed_at
|
|
|
|
token =
|
|
extract_user_token(fn url ->
|
|
Accounts.deliver_user_confirmation_instructions(user, url)
|
|
end)
|
|
|
|
assert {:ok, confirmed} = Accounts.confirm_user(token)
|
|
assert confirmed.confirmed_at
|
|
end
|
|
|
|
test "deliver_user_confirmation_instructions returns error if already confirmed" do
|
|
user = user_fixture()
|
|
|
|
{:ok, confirmed} =
|
|
Accounts.confirm_user(
|
|
extract_user_token(fn url ->
|
|
Accounts.deliver_user_confirmation_instructions(user, url)
|
|
end)
|
|
)
|
|
|
|
# Now delivering again should short-circuit with {:error, :already_confirmed}
|
|
assert {:error, :already_confirmed} =
|
|
Accounts.deliver_user_confirmation_instructions(confirmed, fn _ -> "unused" end)
|
|
end
|
|
|
|
test "confirm_user returns :error for an invalid token" do
|
|
assert Accounts.confirm_user("totally-wrong") == :error
|
|
end
|
|
end
|
|
|
|
describe "reset_user_password/2 + deliver_user_reset_password_instructions/2" do
|
|
test "reset flow end-to-end" do
|
|
user = user_fixture()
|
|
|
|
token =
|
|
extract_user_token(fn url ->
|
|
Accounts.deliver_user_reset_password_instructions(user, url)
|
|
end)
|
|
|
|
# get_user_by_reset_password_token should find the user.
|
|
assert Accounts.get_user_by_reset_password_token(token).id == user.id
|
|
|
|
assert {:ok, _updated} =
|
|
Accounts.reset_user_password(user, %{
|
|
password: "brand new password",
|
|
password_confirmation: "brand new password"
|
|
})
|
|
|
|
assert Accounts.get_user_by_email_and_password(user.email, "brand new password")
|
|
end
|
|
|
|
test "get_user_by_reset_password_token returns nil for bad token" do
|
|
assert Accounts.get_user_by_reset_password_token("bad") == nil
|
|
end
|
|
|
|
test "reset_user_password returns an error changeset for invalid attrs" do
|
|
user = user_fixture()
|
|
|
|
assert {:error, %Ecto.Changeset{}} =
|
|
Accounts.reset_user_password(user, %{
|
|
password: "x",
|
|
password_confirmation: "y"
|
|
})
|
|
end
|
|
end
|
|
end
|