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 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 last completed cycle for member" 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!() # Create member without fee type first to avoid auto-generation member = Mv.Membership.Member |> Ash.Changeset.for_create(:create_member, %{ first_name: "Test", last_name: "Member", email: "test#{System.unique_integer([:positive])}@example.com", join_date: ~D[2022-01-01] }) |> Ash.create!() # Assign fee type after member creation (this may generate cycles, but we'll create our own) member = member |> Ash.Changeset.for_update(:update_member, %{membership_fee_type_id: fee_type.id}) |> Ash.update!() # Delete any auto-generated cycles first cycles = Mv.MembershipFees.MembershipFeeCycle |> Ash.Query.filter(member_id == ^member.id) |> Ash.read!() Enum.each(cycles, fn cycle -> Ash.destroy!(cycle) 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!() 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!() # 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" do fee_type = Mv.MembershipFees.MembershipFeeType |> Ash.Changeset.for_create(:create, %{ name: "Test Type", amount: Decimal.new("50.00"), interval: :yearly }) |> Ash.create!() # Create member without fee type first member = Mv.Membership.Member |> Ash.Changeset.for_create(:create_member, %{ first_name: "Test", last_name: "Member", email: "test#{System.unique_integer([:positive])}@example.com" }) |> Ash.create!() # Assign fee type member = member |> Ash.Changeset.for_update(:update_member, %{membership_fee_type_id: fee_type.id}) |> Ash.update!() # Delete any auto-generated cycles cycles = Mv.MembershipFees.MembershipFeeCycle |> Ash.Query.filter(member_id == ^member.id) |> Ash.read!() Enum.each(cycles, fn cycle -> Ash.destroy!(cycle) 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 current cycle for member" do fee_type = Mv.MembershipFees.MembershipFeeType |> Ash.Changeset.for_create(:create, %{ name: "Test Type", amount: Decimal.new("50.00"), interval: :yearly }) |> Ash.create!() # Create member without fee type first member = Mv.Membership.Member |> Ash.Changeset.for_create(:create_member, %{ first_name: "Test", last_name: "Member", email: "test#{System.unique_integer([:positive])}@example.com", join_date: ~D[2023-01-01] }) |> Ash.create!() # Assign fee type member = member |> Ash.Changeset.for_update(:update_member, %{membership_fee_type_id: fee_type.id}) |> Ash.update!() # Delete any auto-generated cycles cycles = Mv.MembershipFees.MembershipFeeCycle |> Ash.Query.filter(member_id == ^member.id) |> Ash.read!() Enum.each(cycles, fn cycle -> Ash.destroy!(cycle) 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!() # 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