- 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
155 lines
5.5 KiB
Elixir
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
|