Adds ~800 new tests (incl. 27 StreamData properties) across 22 modules that previously had little or no coverage. Highlights: - Pure utility modules (LogSanitizer, PacketFieldWhitelist, PacketSanitizer, DeviceParser, CoordinateUtils, BoundsUtils, ParamUtils, Convert, WeatherUnits) get thorough unit + property coverage including UTF-8 truncation boundaries, antimeridian longitude wrap, Haversine symmetry, and unit-conversion inverses. - JSON view modules (CallsignJSON, ErrorJSON, ChangesetJSON) verified for envelope shape and error templating. - Plugs (HealthCheck, RateLimiter, ApiCSRF) exercised for happy-path and halt paths. - GenServer modules (RegexCache, CleanupScheduler, DeploymentNotifier) tested without touching the supervised singleton where possible. - Drops the orphaned map_live/map_helpers_test.exs whose module name collided with the new live/shared/coordinate_utils_test.exs.
158 lines
5.5 KiB
Elixir
158 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 is_binary(token)
|
|
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 is_binary(encoded)
|
|
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 token_row
|
|
assert token_row.context == "change:new@example.com"
|
|
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
|