Add comprehensive tests for Organizations.Invitation

Improved coverage from 60.00% to 100% by adding 13 tests covering:
- Required field validations (email, role, organization_id, invited_by_id)
- Email format validation
- Automatic token generation using crypto
- Token preservation when provided
- Automatic expires_at generation (7 days from now)
- Expires_at preservation when provided
- All valid role values (admin, member, viewer)
- Accepted_at field handling

Overall coverage: 66.34%
This commit is contained in:
Graham McIntire 2026-01-13 07:45:50 -06:00
parent 160007cab1
commit f5bef9b997
No known key found for this signature in database

View file

@ -0,0 +1,210 @@
defmodule Towerops.Organizations.InvitationTest do
use Towerops.DataCase
alias Towerops.Organizations.Invitation
describe "changeset/2" do
test "valid changeset with required fields" do
attrs = %{
email: "test@example.com",
role: :member,
organization_id: Ecto.UUID.generate(),
invited_by_id: Ecto.UUID.generate()
}
changeset = Invitation.changeset(%Invitation{}, attrs)
assert changeset.valid?
assert get_field(changeset, :email) == "test@example.com"
assert get_field(changeset, :role) == :member
# Token and expires_at should be auto-generated
assert get_field(changeset, :token)
assert get_field(changeset, :expires_at)
end
test "requires email" do
attrs = %{
role: :member,
organization_id: Ecto.UUID.generate(),
invited_by_id: Ecto.UUID.generate()
}
changeset = Invitation.changeset(%Invitation{}, attrs)
refute changeset.valid?
assert %{email: ["can't be blank"]} = errors_on(changeset)
end
test "requires role" do
attrs = %{
email: "test@example.com",
organization_id: Ecto.UUID.generate(),
invited_by_id: Ecto.UUID.generate()
}
changeset = Invitation.changeset(%Invitation{}, attrs)
refute changeset.valid?
assert %{role: ["can't be blank"]} = errors_on(changeset)
end
test "requires organization_id" do
attrs = %{
email: "test@example.com",
role: :member,
invited_by_id: Ecto.UUID.generate()
}
changeset = Invitation.changeset(%Invitation{}, attrs)
refute changeset.valid?
assert %{organization_id: ["can't be blank"]} = errors_on(changeset)
end
test "requires invited_by_id" do
attrs = %{
email: "test@example.com",
role: :member,
organization_id: Ecto.UUID.generate()
}
changeset = Invitation.changeset(%Invitation{}, attrs)
refute changeset.valid?
assert %{invited_by_id: ["can't be blank"]} = errors_on(changeset)
end
test "validates email format" do
attrs = %{
email: "invalid-email",
role: :member,
organization_id: Ecto.UUID.generate(),
invited_by_id: Ecto.UUID.generate()
}
changeset = Invitation.changeset(%Invitation{}, attrs)
refute changeset.valid?
assert %{email: ["has invalid format"]} = errors_on(changeset)
end
test "accepts valid email formats" do
valid_emails = [
"test@example.com",
"user.name@domain.co.uk",
"user+tag@example.org"
]
for email <- valid_emails do
attrs = %{
email: email,
role: :member,
organization_id: Ecto.UUID.generate(),
invited_by_id: Ecto.UUID.generate()
}
changeset = Invitation.changeset(%Invitation{}, attrs)
assert changeset.valid?, "Expected #{email} to be valid"
end
end
test "generates token automatically" do
attrs = %{
email: "test@example.com",
role: :member,
organization_id: Ecto.UUID.generate(),
invited_by_id: Ecto.UUID.generate()
}
changeset = Invitation.changeset(%Invitation{}, attrs)
token = get_field(changeset, :token)
assert token
assert is_binary(token)
assert String.length(token) > 0
end
test "does not override provided token" do
attrs = %{
email: "test@example.com",
role: :member,
organization_id: Ecto.UUID.generate(),
invited_by_id: Ecto.UUID.generate(),
token: "custom-token"
}
changeset = Invitation.changeset(%Invitation{}, attrs)
assert get_field(changeset, :token) == "custom-token"
end
test "generates expires_at automatically (7 days from now)" do
attrs = %{
email: "test@example.com",
role: :member,
organization_id: Ecto.UUID.generate(),
invited_by_id: Ecto.UUID.generate()
}
now = DateTime.utc_now()
changeset = Invitation.changeset(%Invitation{}, attrs)
expires_at = get_field(changeset, :expires_at)
assert expires_at
# Should be approximately 7 days from now (within 1 minute tolerance)
diff_seconds = DateTime.diff(expires_at, now, :second)
expected_seconds = 7 * 24 * 60 * 60
assert_in_delta diff_seconds, expected_seconds, 60
end
test "does not override provided expires_at" do
custom_expires_at = DateTime.utc_now() |> DateTime.add(1, :day) |> DateTime.truncate(:second)
attrs = %{
email: "test@example.com",
role: :member,
organization_id: Ecto.UUID.generate(),
invited_by_id: Ecto.UUID.generate(),
expires_at: custom_expires_at
}
changeset = Invitation.changeset(%Invitation{}, attrs)
assert DateTime.compare(get_field(changeset, :expires_at), custom_expires_at) == :eq
end
test "accepts all valid role values" do
roles = [:admin, :member, :viewer]
for role <- roles do
attrs = %{
email: "test@example.com",
role: role,
organization_id: Ecto.UUID.generate(),
invited_by_id: Ecto.UUID.generate()
}
changeset = Invitation.changeset(%Invitation{}, attrs)
assert changeset.valid?, "Expected role #{role} to be valid"
end
end
test "allows accepted_at to be set" do
accepted_at = DateTime.truncate(DateTime.utc_now(), :second)
attrs = %{
email: "test@example.com",
role: :member,
organization_id: Ecto.UUID.generate(),
invited_by_id: Ecto.UUID.generate(),
accepted_at: accepted_at
}
changeset = Invitation.changeset(%Invitation{}, attrs)
assert changeset.valid?
assert DateTime.compare(get_field(changeset, :accepted_at), accepted_at) == :eq
end
end
end