defmodule Microwaveprop.Accounts.UserApiTokenTest do use Microwaveprop.DataCase, async: true alias Microwaveprop.Accounts.UserApiToken alias Microwaveprop.AccountsFixtures describe "token_prefix/0" do test "is a stable namespaced prefix" do assert UserApiToken.token_prefix() == "mwp_" end end describe "build/2" do setup do %{user: AccountsFixtures.user_fixture()} end test "returns plaintext + valid changeset on good attrs", %{user: user} do assert {:ok, {plaintext, changeset}} = UserApiToken.build(user, %{name: "MyLaptop"}) assert String.starts_with?(plaintext, "mwp_") assert changeset.valid? assert Ecto.Changeset.get_change(changeset, :user_id) == user.id assert Ecto.Changeset.get_change(changeset, :token_hash) == UserApiToken.hash_token(plaintext) end test "name is required", %{user: user} do assert {:error, changeset} = UserApiToken.build(user, %{}) assert %{name: ["can't be blank"]} = errors_on(changeset) refute changeset.valid? assert changeset.action == :insert end test "name length is bounded", %{user: user} do assert {:error, changeset} = UserApiToken.build(user, %{name: String.duplicate("x", 200)}) assert %{name: ["should be at most 100 character(s)"]} = errors_on(changeset) end test "expires_at must be in the future", %{user: user} do past = DateTime.add(DateTime.utc_now(), -60, :second) assert {:error, changeset} = UserApiToken.build(user, %{name: "n", expires_at: past}) assert %{expires_at: ["must be in the future"]} = errors_on(changeset) end test "expires_at in the future is accepted", %{user: user} do future = DateTime.add(DateTime.utc_now(), 3600, :second) assert {:ok, {_plaintext, changeset}} = UserApiToken.build(user, %{name: "n", expires_at: future}) assert changeset.valid? end end describe "hash_token/1" do test "produces a 32-byte sha256 hash" do assert byte_size(UserApiToken.hash_token("abc")) == 32 end test "is deterministic" do assert UserApiToken.hash_token("x") == UserApiToken.hash_token("x") assert UserApiToken.hash_token("x") != UserApiToken.hash_token("y") end end end