- Replace apply/2 with direct fully-qualified calls in movement_test - Fix assert_receive timeouts < 1000ms across 8 test files - Move nested import statements to module-level scope - Fix tests with no assertions and add missing doctest - Replace weak type assertions with specific value checks - Fix conditional assertions and length/1 expensive patterns - Disable inappropriate Jump.CredoChecks.AvoidSocketAssignsInTest - Fix tests not calling application code with credo:disable - Add various credo:disable comments for legitimate patterns
213 lines
6.6 KiB
Elixir
213 lines
6.6 KiB
Elixir
defmodule Aprsme.Accounts.UserTest do
|
|
use Aprsme.DataCase, async: true
|
|
|
|
alias Aprsme.Accounts.User
|
|
|
|
describe "registration_changeset/2" do
|
|
test "requires callsign" do
|
|
changeset = User.registration_changeset(%User{}, %{})
|
|
errors = errors_on(changeset)
|
|
assert Map.has_key?(errors, :callsign)
|
|
assert "can't be blank" in errors.callsign
|
|
end
|
|
|
|
test "validates callsign format" do
|
|
invalid_callsigns = ["123", "TOOLONGCALLSIGN", "NO SPACES", "nodigit"]
|
|
|
|
for callsign <- invalid_callsigns do
|
|
changeset =
|
|
User.registration_changeset(%User{}, %{
|
|
email: "test@example.com",
|
|
password: "valid_password123",
|
|
callsign: callsign
|
|
})
|
|
|
|
errors = errors_on(changeset)
|
|
assert Map.has_key?(errors, :callsign), "Expected callsign error for: #{callsign}"
|
|
assert "must be a valid amateur radio callsign" in errors.callsign
|
|
end
|
|
end
|
|
|
|
test "validates empty callsign" do
|
|
changeset =
|
|
User.registration_changeset(%User{}, %{
|
|
email: "test@example.com",
|
|
password: "valid_password123",
|
|
callsign: ""
|
|
})
|
|
|
|
errors = errors_on(changeset)
|
|
assert Map.has_key?(errors, :callsign)
|
|
assert "can't be blank" in errors.callsign
|
|
end
|
|
|
|
test "accepts valid callsigns" do
|
|
valid_callsigns = ["K1ABC", "W2XYZ", "AA3BB", "KG4AAA", "N7PQR", "VE3ABC"]
|
|
|
|
for callsign <- valid_callsigns do
|
|
changeset =
|
|
User.registration_changeset(%User{}, %{
|
|
email: "test@example.com",
|
|
password: "valid_password123",
|
|
callsign: callsign
|
|
})
|
|
|
|
assert changeset.valid?
|
|
end
|
|
end
|
|
|
|
test "converts callsign to uppercase" do
|
|
changeset =
|
|
User.registration_changeset(%User{}, %{
|
|
email: "test@example.com",
|
|
password: "valid_password123",
|
|
callsign: "k1abc"
|
|
})
|
|
|
|
assert get_change(changeset, :callsign) == "K1ABC"
|
|
end
|
|
end
|
|
|
|
describe "callsign_changeset/2" do
|
|
test "requires callsign to change" do
|
|
user = %User{callsign: "K1ABC"}
|
|
changeset = User.callsign_changeset(user, %{callsign: "K1ABC"})
|
|
errors = errors_on(changeset)
|
|
assert Map.has_key?(errors, :callsign)
|
|
assert "did not change" in errors.callsign
|
|
end
|
|
|
|
test "validates new callsign format" do
|
|
user = %User{callsign: "K1ABC"}
|
|
|
|
changeset = User.callsign_changeset(user, %{callsign: "INVALID"})
|
|
errors = errors_on(changeset)
|
|
assert Map.has_key?(errors, :callsign)
|
|
assert "must be a valid amateur radio callsign" in errors.callsign
|
|
end
|
|
|
|
test "accepts valid callsign change" do
|
|
user = %User{callsign: "K1ABC"}
|
|
changeset = User.callsign_changeset(user, %{callsign: "W2XYZ"})
|
|
assert changeset.valid?
|
|
assert get_change(changeset, :callsign) == "W2XYZ"
|
|
end
|
|
end
|
|
|
|
describe "email_changeset/2" do
|
|
test "requires email to change" do
|
|
user = %User{email: "a@b.com"}
|
|
changeset = User.email_changeset(user, %{email: "a@b.com"})
|
|
errors = errors_on(changeset)
|
|
assert Map.has_key?(errors, :email)
|
|
assert "did not change" in errors.email
|
|
end
|
|
|
|
test "validates format of the new email" do
|
|
user = %User{email: "a@b.com"}
|
|
changeset = User.email_changeset(user, %{email: "bad email"})
|
|
errors = errors_on(changeset)
|
|
assert "must have the @ sign and no spaces" in errors.email
|
|
end
|
|
end
|
|
|
|
describe "password_changeset/2" do
|
|
test "requires matching confirmation" do
|
|
user = %User{}
|
|
|
|
changeset =
|
|
User.password_changeset(user, %{
|
|
"password" => "abcdefghijkl",
|
|
"password_confirmation" => "different"
|
|
})
|
|
|
|
errors = errors_on(changeset)
|
|
assert "does not match password" in errors.password_confirmation
|
|
end
|
|
|
|
test "hashes password when valid" do
|
|
user = %User{}
|
|
|
|
changeset =
|
|
User.password_changeset(user, %{
|
|
"password" => "abcdefghijkl",
|
|
"password_confirmation" => "abcdefghijkl"
|
|
})
|
|
|
|
assert changeset.valid?
|
|
assert byte_size(get_change(changeset, :hashed_password)) > 0
|
|
# Plaintext password is dropped after hashing.
|
|
refute get_change(changeset, :password)
|
|
end
|
|
end
|
|
|
|
describe "confirm_changeset/1" do
|
|
test "sets confirmed_at to the current time truncated to seconds" do
|
|
changeset = User.confirm_changeset(%User{})
|
|
confirmed_at = get_change(changeset, :confirmed_at)
|
|
assert %NaiveDateTime{} = confirmed_at
|
|
# No microseconds — truncated.
|
|
assert confirmed_at.microsecond == {0, 0}
|
|
end
|
|
end
|
|
|
|
describe "valid_password?/2" do
|
|
test "returns false when the user has no hashed password" do
|
|
refute User.valid_password?(%User{hashed_password: nil}, "any")
|
|
end
|
|
|
|
test "returns false when the provided password is empty" do
|
|
user = %User{hashed_password: Bcrypt.hash_pwd_salt("hello world!")}
|
|
refute User.valid_password?(user, "")
|
|
end
|
|
|
|
test "returns true for the correct password" do
|
|
user = %User{hashed_password: Bcrypt.hash_pwd_salt("hello world!")}
|
|
assert User.valid_password?(user, "hello world!")
|
|
end
|
|
|
|
test "returns false for the wrong password" do
|
|
user = %User{hashed_password: Bcrypt.hash_pwd_salt("hello world!")}
|
|
refute User.valid_password?(user, "wrong")
|
|
end
|
|
end
|
|
|
|
describe "validate_current_password/2" do
|
|
test "adds an error when the password is wrong" do
|
|
user = %User{hashed_password: Bcrypt.hash_pwd_salt("hello world!")}
|
|
changeset = Ecto.Changeset.change(user)
|
|
changeset = User.validate_current_password(changeset, "wrong")
|
|
errors = errors_on(changeset)
|
|
assert "is not valid" in errors.current_password
|
|
end
|
|
|
|
test "does not add an error when the password is correct" do
|
|
user = %User{hashed_password: Bcrypt.hash_pwd_salt("hello world!")}
|
|
changeset = Ecto.Changeset.change(user)
|
|
changeset = User.validate_current_password(changeset, "hello world!")
|
|
assert changeset.valid?
|
|
end
|
|
end
|
|
|
|
describe "Inspect protocol" do
|
|
test "redacts the hashed and plaintext passwords" do
|
|
user = %User{
|
|
email: "a@b.com",
|
|
hashed_password: "super-secret-hash",
|
|
password: "super-secret"
|
|
}
|
|
|
|
output = inspect(user)
|
|
refute output =~ "super-secret-hash"
|
|
refute output =~ "super-secret"
|
|
# The email is non-redacted, so it should still appear.
|
|
assert output =~ "a@b.com"
|
|
|
|
changeset =
|
|
User.password_changeset(%User{}, %{"password" => "newpass12345", "password_confirmation" => "newpass12345"})
|
|
|
|
assert changeset.valid?
|
|
assert byte_size(get_change(changeset, :hashed_password)) > 0
|
|
end
|
|
end
|
|
end
|