aprs.me/test/aprsme/accounts/user_token_test.exs
Graham McIntire b86153cd27
Fix all mix credo --strict warnings (188 → 0)
- 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
2026-06-12 16:27:20 -05:00

155 lines
5.5 KiB
Elixir

defmodule Aprsme.Accounts.UserTokenTest do
use Aprsme.DataCase, async: true
import Aprsme.AccountsFixtures
alias Aprsme.Accounts.UserToken
alias Aprsme.Repo
describe "build_session_token/1" do
test "returns a binary token and a UserToken struct with context 'session'" do
user = user_fixture()
{token, user_token} = UserToken.build_session_token(user)
assert byte_size(token) == 32
assert user_token.token == token
assert user_token.context == "session"
assert user_token.user_id == user.id
assert is_nil(user_token.sent_to)
end
test "produces unique tokens each call" do
user = user_fixture()
{t1, _} = UserToken.build_session_token(user)
{t2, _} = UserToken.build_session_token(user)
refute t1 == t2
end
end
describe "verify_session_token_query/1" do
test "returns the user when token is valid" do
user = user_fixture()
{token, user_token} = UserToken.build_session_token(user)
Repo.insert!(user_token)
{:ok, query} = UserToken.verify_session_token_query(token)
loaded = Repo.one(query)
assert loaded.id == user.id
end
test "returns nil for an unknown token" do
{:ok, query} = UserToken.verify_session_token_query(:crypto.strong_rand_bytes(32))
assert Repo.one(query) == nil
end
end
describe "build_email_token/2" do
test "returns a URL-safe base64 token and a hashed UserToken" do
user = user_fixture()
{encoded, user_token} = UserToken.build_email_token(user, "confirm")
assert {:ok, _} = Base.url_decode64(encoded, padding: false)
assert user_token.context == "confirm"
assert user_token.sent_to == user.email
assert user_token.user_id == user.id
# The stored token is a SHA-256 digest (32 bytes), not the encoded plaintext.
assert byte_size(user_token.token) == 32
refute user_token.token == encoded
end
test "supports reset_password context" do
user = user_fixture()
{_token, user_token} = UserToken.build_email_token(user, "reset_password")
assert user_token.context == "reset_password"
end
end
describe "verify_email_token_query/2" do
test "returns :error for malformed tokens" do
assert UserToken.verify_email_token_query("not-base64!", "confirm") == :error
end
test "returns {:ok, query} that finds the user for a valid token" do
user = user_fixture()
{encoded, user_token} = UserToken.build_email_token(user, "confirm")
Repo.insert!(user_token)
{:ok, query} = UserToken.verify_email_token_query(encoded, "confirm")
assert Repo.one(query).id == user.id
end
test "returns a query matching no user for a valid-looking but unknown token" do
# Randomly-generated encoded token that doesn't exist in the DB.
encoded = 32 |> :crypto.strong_rand_bytes() |> Base.url_encode64(padding: false)
{:ok, query} = UserToken.verify_email_token_query(encoded, "confirm")
assert Repo.one(query) == nil
end
end
describe "verify_change_email_token_query/2" do
test "returns :error for malformed tokens" do
assert UserToken.verify_change_email_token_query("not-base64!", "change:new@example.com") == :error
end
test "accepts a well-formed token for a change: context" do
user = user_fixture()
# Reuse build_email_token/2 (same hashing) for a realistic token.
{encoded, user_token} = UserToken.build_email_token(user, "change:new@example.com")
Repo.insert!(user_token)
{:ok, query} = UserToken.verify_change_email_token_query(encoded, "change:new@example.com")
# verify_change_email_token_query doesn't select :user, it selects tokens.
token_row = Repo.one(query)
assert %_{context: "change:new@example.com"} = token_row
end
end
describe "token_and_context_query/2" do
test "returns a query scoped by token and context" do
user = user_fixture()
{token, user_token} = UserToken.build_session_token(user)
Repo.insert!(user_token)
query = UserToken.token_and_context_query(token, "session")
found = Repo.one(query)
assert found.token == token
assert found.context == "session"
end
test "returns nothing for mismatched context" do
user = user_fixture()
{token, user_token} = UserToken.build_session_token(user)
Repo.insert!(user_token)
assert token |> UserToken.token_and_context_query("confirm") |> Repo.one() == nil
end
end
describe "user_and_contexts_query/2" do
setup do
user = user_fixture()
{_token, session_token} = UserToken.build_session_token(user)
{_token, confirm_token} = UserToken.build_email_token(user, "confirm")
Repo.insert!(session_token)
Repo.insert!(confirm_token)
{:ok, user: user}
end
test "with :all returns every token for the user", %{user: user} do
tokens = user |> UserToken.user_and_contexts_query(:all) |> Repo.all()
assert length(tokens) == 2
contexts = tokens |> Enum.map(& &1.context) |> Enum.sort()
assert contexts == ["confirm", "session"]
end
test "with a specific context list filters", %{user: user} do
tokens = user |> UserToken.user_and_contexts_query(["session"]) |> Repo.all()
assert length(tokens) == 1
assert hd(tokens).context == "session"
end
test "with an unmatched context returns empty", %{user: user} do
assert user |> UserToken.user_and_contexts_query(["reset_password"]) |> Repo.all() == []
end
end
end