- get_last_completed_cycle/2 and get_current_cycle/2 return nil when member is nil. - Avoids FunctionClauseError when MemberLive.Show receives no member (e.g. after redirect or policy filter). Add unit tests for nil member.
272 lines
8.9 KiB
Elixir
272 lines
8.9 KiB
Elixir
defmodule MvWeb.Helpers.MembershipFeeHelpersTest do
|
|
@moduledoc """
|
|
Tests for MembershipFeeHelpers module.
|
|
"""
|
|
use Mv.DataCase, async: true
|
|
|
|
require Ash.Query
|
|
|
|
alias MvWeb.Helpers.MembershipFeeHelpers
|
|
alias Mv.MembershipFees.CalendarCycles
|
|
|
|
setup do
|
|
system_actor = Mv.Helpers.SystemActor.get_system_actor()
|
|
%{actor: system_actor}
|
|
end
|
|
|
|
describe "format_currency/1" do
|
|
test "formats decimal amount correctly" do
|
|
assert MembershipFeeHelpers.format_currency(Decimal.new("60.00")) == "60,00 €"
|
|
assert MembershipFeeHelpers.format_currency(Decimal.new("5.5")) == "5,50 €"
|
|
assert MembershipFeeHelpers.format_currency(Decimal.new("100")) == "100,00 €"
|
|
assert MembershipFeeHelpers.format_currency(Decimal.new("0.99")) == "0,99 €"
|
|
end
|
|
end
|
|
|
|
describe "format_interval/1" do
|
|
test "formats all interval types correctly" do
|
|
assert MembershipFeeHelpers.format_interval(:monthly) == "Monthly"
|
|
assert MembershipFeeHelpers.format_interval(:quarterly) == "Quarterly"
|
|
assert MembershipFeeHelpers.format_interval(:half_yearly) == "Half-yearly"
|
|
assert MembershipFeeHelpers.format_interval(:yearly) == "Yearly"
|
|
end
|
|
end
|
|
|
|
describe "format_cycle_range/2" do
|
|
test "formats yearly cycle range correctly" do
|
|
cycle_start = ~D[2024-01-01]
|
|
interval = :yearly
|
|
_cycle_end = CalendarCycles.calculate_cycle_end(cycle_start, interval)
|
|
|
|
result = MembershipFeeHelpers.format_cycle_range(cycle_start, interval)
|
|
assert result =~ "2024"
|
|
assert result =~ "01.01"
|
|
assert result =~ "31.12"
|
|
end
|
|
|
|
test "formats quarterly cycle range correctly" do
|
|
cycle_start = ~D[2024-01-01]
|
|
interval = :quarterly
|
|
_cycle_end = CalendarCycles.calculate_cycle_end(cycle_start, interval)
|
|
|
|
result = MembershipFeeHelpers.format_cycle_range(cycle_start, interval)
|
|
assert result =~ "2024"
|
|
assert result =~ "01.01"
|
|
assert result =~ "31.03"
|
|
end
|
|
|
|
test "formats monthly cycle range correctly" do
|
|
cycle_start = ~D[2024-03-01]
|
|
interval = :monthly
|
|
_cycle_end = CalendarCycles.calculate_cycle_end(cycle_start, interval)
|
|
|
|
result = MembershipFeeHelpers.format_cycle_range(cycle_start, interval)
|
|
assert result =~ "2024"
|
|
assert result =~ "01.03"
|
|
assert result =~ "31.03"
|
|
end
|
|
end
|
|
|
|
describe "get_last_completed_cycle/2" do
|
|
test "returns nil when member is nil" do
|
|
assert MembershipFeeHelpers.get_last_completed_cycle(nil) == nil
|
|
assert MembershipFeeHelpers.get_last_completed_cycle(nil, Date.utc_today()) == nil
|
|
end
|
|
|
|
test "returns last completed cycle for member", %{actor: actor} do
|
|
# Create test data
|
|
fee_type =
|
|
Mv.MembershipFees.MembershipFeeType
|
|
|> Ash.Changeset.for_create(:create, %{
|
|
name: "Test Type",
|
|
amount: Decimal.new("50.00"),
|
|
interval: :yearly
|
|
})
|
|
|> Ash.create!(actor: actor)
|
|
|
|
# Create member without fee type first to avoid auto-generation
|
|
{:ok, member} =
|
|
Mv.Membership.create_member(
|
|
%{
|
|
first_name: "Test",
|
|
last_name: "Member",
|
|
email: "test#{System.unique_integer([:positive])}@example.com",
|
|
join_date: ~D[2022-01-01]
|
|
},
|
|
actor: actor
|
|
)
|
|
|
|
# Assign fee type after member creation (this may generate cycles, but we'll create our own)
|
|
{:ok, member} =
|
|
Mv.Membership.update_member(member, %{membership_fee_type_id: fee_type.id}, actor: actor)
|
|
|
|
# Delete any auto-generated cycles first
|
|
cycles =
|
|
Mv.MembershipFees.MembershipFeeCycle
|
|
|> Ash.Query.filter(member_id == ^member.id)
|
|
|> Ash.read!(actor: actor)
|
|
|
|
Enum.each(cycles, fn cycle -> Ash.destroy!(cycle, actor: actor) end)
|
|
|
|
# Create cycles manually
|
|
_cycle_2022 =
|
|
Mv.MembershipFees.MembershipFeeCycle
|
|
|> Ash.Changeset.for_create(:create, %{
|
|
cycle_start: ~D[2022-01-01],
|
|
amount: Decimal.new("50.00"),
|
|
member_id: member.id,
|
|
membership_fee_type_id: fee_type.id,
|
|
status: :paid
|
|
})
|
|
|> Ash.create!(actor: actor)
|
|
|
|
cycle_2023 =
|
|
Mv.MembershipFees.MembershipFeeCycle
|
|
|> Ash.Changeset.for_create(:create, %{
|
|
cycle_start: ~D[2023-01-01],
|
|
amount: Decimal.new("50.00"),
|
|
member_id: member.id,
|
|
membership_fee_type_id: fee_type.id,
|
|
status: :paid
|
|
})
|
|
|> Ash.create!(actor: actor)
|
|
|
|
# Load cycles with membership_fee_type relationship
|
|
member =
|
|
member
|
|
|> Ash.load!(membership_fee_cycles: [:membership_fee_type])
|
|
|> Ash.load!(:membership_fee_type)
|
|
|
|
# Use a fixed date in 2024 to ensure 2023 is last completed
|
|
today = ~D[2024-06-15]
|
|
last_cycle = MembershipFeeHelpers.get_last_completed_cycle(member, today)
|
|
|
|
assert last_cycle.id == cycle_2023.id
|
|
end
|
|
|
|
test "returns nil if no cycles exist", %{actor: actor} do
|
|
fee_type =
|
|
Mv.MembershipFees.MembershipFeeType
|
|
|> Ash.Changeset.for_create(:create, %{
|
|
name: "Test Type",
|
|
amount: Decimal.new("50.00"),
|
|
interval: :yearly
|
|
})
|
|
|> Ash.create!(actor: actor)
|
|
|
|
# Create member without fee type first
|
|
{:ok, member} =
|
|
Mv.Membership.create_member(
|
|
%{
|
|
first_name: "Test",
|
|
last_name: "Member",
|
|
email: "test#{System.unique_integer([:positive])}@example.com"
|
|
},
|
|
actor: actor
|
|
)
|
|
|
|
# Assign fee type
|
|
{:ok, member} =
|
|
Mv.Membership.update_member(member, %{membership_fee_type_id: fee_type.id}, actor: actor)
|
|
|
|
# Delete any auto-generated cycles
|
|
cycles =
|
|
Mv.MembershipFees.MembershipFeeCycle
|
|
|> Ash.Query.filter(member_id == ^member.id)
|
|
|> Ash.read!(actor: actor)
|
|
|
|
Enum.each(cycles, fn cycle -> Ash.destroy!(cycle, actor: actor) end)
|
|
|
|
# Load cycles and fee type (will be empty)
|
|
member =
|
|
member
|
|
|> Ash.load!(membership_fee_cycles: [:membership_fee_type])
|
|
|> Ash.load!(:membership_fee_type)
|
|
|
|
last_cycle = MembershipFeeHelpers.get_last_completed_cycle(member, Date.utc_today())
|
|
assert last_cycle == nil
|
|
end
|
|
end
|
|
|
|
describe "get_current_cycle/2" do
|
|
test "returns nil when member is nil" do
|
|
assert MembershipFeeHelpers.get_current_cycle(nil) == nil
|
|
assert MembershipFeeHelpers.get_current_cycle(nil, Date.utc_today()) == nil
|
|
end
|
|
|
|
test "returns current cycle for member", %{actor: actor} do
|
|
fee_type =
|
|
Mv.MembershipFees.MembershipFeeType
|
|
|> Ash.Changeset.for_create(:create, %{
|
|
name: "Test Type",
|
|
amount: Decimal.new("50.00"),
|
|
interval: :yearly
|
|
})
|
|
|> Ash.create!(actor: actor)
|
|
|
|
# Create member without fee type first
|
|
{:ok, member} =
|
|
Mv.Membership.create_member(
|
|
%{
|
|
first_name: "Test",
|
|
last_name: "Member",
|
|
email: "test#{System.unique_integer([:positive])}@example.com",
|
|
join_date: ~D[2023-01-01]
|
|
},
|
|
actor: actor
|
|
)
|
|
|
|
# Assign fee type
|
|
{:ok, member} =
|
|
Mv.Membership.update_member(member, %{membership_fee_type_id: fee_type.id}, actor: actor)
|
|
|
|
# Delete any auto-generated cycles
|
|
cycles =
|
|
Mv.MembershipFees.MembershipFeeCycle
|
|
|> Ash.Query.filter(member_id == ^member.id)
|
|
|> Ash.read!(actor: actor)
|
|
|
|
Enum.each(cycles, fn cycle -> Ash.destroy!(cycle, actor: actor) end)
|
|
|
|
today = Date.utc_today()
|
|
current_year_start = %{today | month: 1, day: 1}
|
|
|
|
current_cycle =
|
|
Mv.MembershipFees.MembershipFeeCycle
|
|
|> Ash.Changeset.for_create(:create, %{
|
|
cycle_start: current_year_start,
|
|
amount: Decimal.new("50.00"),
|
|
member_id: member.id,
|
|
membership_fee_type_id: fee_type.id,
|
|
status: :unpaid
|
|
})
|
|
|> Ash.create!(actor: actor)
|
|
|
|
# Load cycles with membership_fee_type relationship
|
|
member =
|
|
member
|
|
|> Ash.load!(membership_fee_cycles: [:membership_fee_type])
|
|
|> Ash.load!(:membership_fee_type)
|
|
|
|
result = MembershipFeeHelpers.get_current_cycle(member, today)
|
|
|
|
assert result.id == current_cycle.id
|
|
end
|
|
end
|
|
|
|
describe "status_color/1" do
|
|
test "returns correct color classes for statuses" do
|
|
assert MembershipFeeHelpers.status_color(:paid) == "badge-success"
|
|
assert MembershipFeeHelpers.status_color(:unpaid) == "badge-error"
|
|
assert MembershipFeeHelpers.status_color(:suspended) == "badge-ghost"
|
|
end
|
|
end
|
|
|
|
describe "status_icon/1" do
|
|
test "returns correct icon names for statuses" do
|
|
assert MembershipFeeHelpers.status_icon(:paid) == "hero-check-circle"
|
|
assert MembershipFeeHelpers.status_icon(:unpaid) == "hero-x-circle"
|
|
assert MembershipFeeHelpers.status_icon(:suspended) == "hero-pause-circle"
|
|
end
|
|
end
|
|
end
|