mitgliederverwaltung/test/membership_fees/membership_fee_cycle_test.exs
Moritz ebbf347e42
All checks were successful
continuous-integration/drone/push Build is passing
fix(membership-fees): add DB constraints for enum and decimal precision
2025-12-11 18:46:48 +01:00

282 lines
8.4 KiB
Elixir

defmodule Mv.MembershipFees.MembershipFeeCycleTest do
@moduledoc """
Tests for MembershipFeeCycle resource.
"""
use Mv.DataCase, async: true
alias Mv.MembershipFees.MembershipFeeCycle
alias Mv.MembershipFees.MembershipFeeType
alias Mv.Membership.Member
setup do
# Create a member for testing
{:ok, member} =
Ash.create(Member, %{
first_name: "Test",
last_name: "Member",
email: "test.member.#{System.unique_integer([:positive])}@example.com"
})
# Create a fee type for testing
{:ok, fee_type} =
Ash.create(MembershipFeeType, %{
name: "Test Fee Type #{System.unique_integer([:positive])}",
amount: Decimal.new("100.00"),
interval: :monthly
})
%{member: member, fee_type: fee_type}
end
describe "create MembershipFeeCycle" do
test "can create cycle with valid attributes", %{member: member, fee_type: fee_type} do
attrs = %{
cycle_start: ~D[2025-01-01],
amount: Decimal.new("100.00"),
member_id: member.id,
membership_fee_type_id: fee_type.id
}
assert {:ok, %MembershipFeeCycle{} = cycle} = Ash.create(MembershipFeeCycle, attrs)
assert cycle.cycle_start == ~D[2025-01-01]
assert Decimal.equal?(cycle.amount, Decimal.new("100.00"))
assert cycle.member_id == member.id
assert cycle.membership_fee_type_id == fee_type.id
end
test "can create cycle with notes", %{member: member, fee_type: fee_type} do
attrs = %{
cycle_start: ~D[2025-01-01],
amount: Decimal.new("100.00"),
member_id: member.id,
membership_fee_type_id: fee_type.id,
notes: "First payment cycle"
}
assert {:ok, cycle} = Ash.create(MembershipFeeCycle, attrs)
assert cycle.notes == "First payment cycle"
end
test "requires cycle_start", %{member: member, fee_type: fee_type} do
attrs = %{
amount: Decimal.new("100.00"),
member_id: member.id,
membership_fee_type_id: fee_type.id
}
assert {:error, error} = Ash.create(MembershipFeeCycle, attrs)
assert error_on_field?(error, :cycle_start)
end
test "requires amount", %{member: member, fee_type: fee_type} do
attrs = %{
cycle_start: ~D[2025-01-01],
member_id: member.id,
membership_fee_type_id: fee_type.id
}
assert {:error, error} = Ash.create(MembershipFeeCycle, attrs)
assert error_on_field?(error, :amount)
end
test "requires member_id", %{fee_type: fee_type} do
attrs = %{
cycle_start: ~D[2025-01-01],
amount: Decimal.new("100.00"),
membership_fee_type_id: fee_type.id
}
assert {:error, error} = Ash.create(MembershipFeeCycle, attrs)
assert error_on_field?(error, :member_id) or error_on_field?(error, :member)
end
test "requires membership_fee_type_id", %{member: member} do
attrs = %{
cycle_start: ~D[2025-01-01],
amount: Decimal.new("100.00"),
member_id: member.id
}
assert {:error, error} = Ash.create(MembershipFeeCycle, attrs)
assert error_on_field?(error, :membership_fee_type_id) or
error_on_field?(error, :membership_fee_type)
end
test "status defaults to :unpaid", %{member: member, fee_type: fee_type} do
attrs = %{
cycle_start: ~D[2025-01-01],
amount: Decimal.new("100.00"),
member_id: member.id,
membership_fee_type_id: fee_type.id
}
assert {:ok, cycle} = Ash.create(MembershipFeeCycle, attrs)
assert cycle.status == :unpaid
end
test "validates status enum values - unpaid", %{member: member, fee_type: fee_type} do
attrs = %{
cycle_start: ~D[2025-01-01],
amount: Decimal.new("100.00"),
member_id: member.id,
membership_fee_type_id: fee_type.id,
status: :unpaid
}
assert {:ok, cycle} = Ash.create(MembershipFeeCycle, attrs)
assert cycle.status == :unpaid
end
test "validates status enum values - paid", %{member: member, fee_type: fee_type} do
attrs = %{
cycle_start: ~D[2025-02-01],
amount: Decimal.new("100.00"),
member_id: member.id,
membership_fee_type_id: fee_type.id,
status: :paid
}
assert {:ok, cycle} = Ash.create(MembershipFeeCycle, attrs)
assert cycle.status == :paid
end
test "validates status enum values - suspended", %{member: member, fee_type: fee_type} do
attrs = %{
cycle_start: ~D[2025-03-01],
amount: Decimal.new("100.00"),
member_id: member.id,
membership_fee_type_id: fee_type.id,
status: :suspended
}
assert {:ok, cycle} = Ash.create(MembershipFeeCycle, attrs)
assert cycle.status == :suspended
end
test "rejects invalid status values", %{member: member, fee_type: fee_type} do
attrs = %{
cycle_start: ~D[2025-01-01],
amount: Decimal.new("100.00"),
member_id: member.id,
membership_fee_type_id: fee_type.id,
status: :cancelled
}
assert {:error, error} = Ash.create(MembershipFeeCycle, attrs)
assert error_on_field?(error, :status)
end
test "rejects negative amount", %{member: member, fee_type: fee_type} do
attrs = %{
cycle_start: ~D[2025-04-01],
amount: Decimal.new("-50.00"),
member_id: member.id,
membership_fee_type_id: fee_type.id
}
assert {:error, error} = Ash.create(MembershipFeeCycle, attrs)
assert error_on_field?(error, :amount)
end
test "accepts zero amount", %{member: member, fee_type: fee_type} do
attrs = %{
cycle_start: ~D[2025-05-01],
amount: Decimal.new("0.00"),
member_id: member.id,
membership_fee_type_id: fee_type.id
}
assert {:ok, cycle} = Ash.create(MembershipFeeCycle, attrs)
assert Decimal.equal?(cycle.amount, Decimal.new("0.00"))
end
end
describe "uniqueness constraint" do
test "cannot create duplicate cycle for same member and cycle_start", %{
member: member,
fee_type: fee_type
} do
attrs = %{
cycle_start: ~D[2025-01-01],
amount: Decimal.new("100.00"),
member_id: member.id,
membership_fee_type_id: fee_type.id
}
assert {:ok, _cycle1} = Ash.create(MembershipFeeCycle, attrs)
assert {:error, error} = Ash.create(MembershipFeeCycle, attrs)
# Should fail due to uniqueness constraint
assert is_struct(error, Ash.Error.Invalid)
end
test "can create cycles for same member with different cycle_start", %{
member: member,
fee_type: fee_type
} do
attrs1 = %{
cycle_start: ~D[2025-01-01],
amount: Decimal.new("100.00"),
member_id: member.id,
membership_fee_type_id: fee_type.id
}
attrs2 = %{
cycle_start: ~D[2025-02-01],
amount: Decimal.new("100.00"),
member_id: member.id,
membership_fee_type_id: fee_type.id
}
assert {:ok, _cycle1} = Ash.create(MembershipFeeCycle, attrs1)
assert {:ok, _cycle2} = Ash.create(MembershipFeeCycle, attrs2)
end
test "can create cycles for different members with same cycle_start", %{fee_type: fee_type} do
{:ok, member1} =
Ash.create(Member, %{
first_name: "Member",
last_name: "One",
email: "member.one.#{System.unique_integer([:positive])}@example.com"
})
{:ok, member2} =
Ash.create(Member, %{
first_name: "Member",
last_name: "Two",
email: "member.two.#{System.unique_integer([:positive])}@example.com"
})
attrs1 = %{
cycle_start: ~D[2025-01-01],
amount: Decimal.new("100.00"),
member_id: member1.id,
membership_fee_type_id: fee_type.id
}
attrs2 = %{
cycle_start: ~D[2025-01-01],
amount: Decimal.new("100.00"),
member_id: member2.id,
membership_fee_type_id: fee_type.id
}
assert {:ok, _cycle1} = Ash.create(MembershipFeeCycle, attrs1)
assert {:ok, _cycle2} = Ash.create(MembershipFeeCycle, attrs2)
end
end
# Helper to check if an error occurred on a specific field
defp error_on_field?(%Ash.Error.Invalid{} = error, field) do
Enum.any?(error.errors, fn e ->
case e do
%{field: ^field} -> true
%{fields: fields} when is_list(fields) -> field in fields
_ -> false
end
end)
end
defp error_on_field?(_, _), do: false
end