defmodule MvWeb.MemberLive.ShowMembershipFeesTest do @moduledoc """ Tests for membership fees section in member detail view. """ use MvWeb.ConnCase, async: false import Phoenix.LiveViewTest alias Mv.Membership.Member alias Mv.MembershipFees.MembershipFeeType alias Mv.MembershipFees.MembershipFeeCycle require Ash.Query setup do # Create admin user {:ok, user} = Mv.Accounts.User |> Ash.Changeset.for_create(:register_with_password, %{ email: "admin#{System.unique_integer([:positive])}@mv.local", password: "testpassword123" }) |> Ash.create() conn = log_in_user(build_conn(), user) %{conn: conn, user: user} end # Helper to create a membership fee type defp create_fee_type(attrs) do default_attrs = %{ name: "Test Fee Type #{System.unique_integer([:positive])}", amount: Decimal.new("50.00"), interval: :yearly } attrs = Map.merge(default_attrs, attrs) MembershipFeeType |> Ash.Changeset.for_create(:create, attrs) |> Ash.create!() end # Helper to create a member defp create_member(attrs) do default_attrs = %{ first_name: "Test", last_name: "Member", email: "test.member.#{System.unique_integer([:positive])}@example.com" } attrs = Map.merge(default_attrs, attrs) Member |> Ash.Changeset.for_create(:create_member, attrs) |> Ash.create!() end # Helper to create a cycle defp create_cycle(member, fee_type, attrs) do default_attrs = %{ cycle_start: ~D[2023-01-01], amount: Decimal.new("50.00"), member_id: member.id, membership_fee_type_id: fee_type.id, status: :unpaid } attrs = Map.merge(default_attrs, attrs) MembershipFeeCycle |> Ash.Changeset.for_create(:create, attrs) |> Ash.create!() end describe "cycles table display" do test "displays all cycles for member", %{conn: conn} do fee_type = create_fee_type(%{interval: :yearly}) member = create_member(%{membership_fee_type_id: fee_type.id}) _cycle1 = create_cycle(member, fee_type, %{cycle_start: ~D[2022-01-01], status: :paid}) _cycle2 = create_cycle(member, fee_type, %{cycle_start: ~D[2023-01-01], status: :unpaid}) {:ok, _view, html} = live(conn, "/members/#{member.id}") # Should show cycles table assert html =~ "Membership Fees" || html =~ "Mitgliedsbeiträge" assert html =~ "2022" || html =~ "2023" end test "table columns show correct data", %{conn: conn} do fee_type = create_fee_type(%{interval: :yearly, amount: Decimal.new("60.00")}) member = create_member(%{membership_fee_type_id: fee_type.id}) create_cycle(member, fee_type, %{ cycle_start: ~D[2023-01-01], amount: Decimal.new("60.00"), status: :paid }) {:ok, _view, html} = live(conn, "/members/#{member.id}") # Should show interval, amount, status assert html =~ "Yearly" || html =~ "Jährlich" assert html =~ "60" || html =~ "60,00" assert html =~ "paid" || html =~ "bezahlt" end end describe "membership fee type dropdown" do test "shows only same-interval types", %{conn: conn} do yearly_type = create_fee_type(%{interval: :yearly, name: "Yearly Type"}) _monthly_type = create_fee_type(%{interval: :monthly, name: "Monthly Type"}) member = create_member(%{membership_fee_type_id: yearly_type.id}) {:ok, _view, html} = live(conn, "/members/#{member.id}") # Should show yearly type but not monthly assert html =~ "Yearly Type" refute html =~ "Monthly Type" end test "shows warning if different interval selected", %{conn: conn} do yearly_type = create_fee_type(%{interval: :yearly, name: "Yearly Type"}) monthly_type = create_fee_type(%{interval: :monthly, name: "Monthly Type"}) member = create_member(%{membership_fee_type_id: yearly_type.id}) {:ok, view, _html} = live(conn, "/members/#{member.id}") # Try to select monthly type (should show warning) # Note: This test may need adjustment based on actual implementation html = view |> form("form", %{"membership_fee_type_id" => monthly_type.id}) |> render_change() assert html =~ "Warning" || html =~ "Warnung" || html =~ "not allowed" end end describe "status change actions" do test "mark as paid works", %{conn: conn} do fee_type = create_fee_type(%{interval: :yearly}) member = create_member(%{membership_fee_type_id: fee_type.id}) cycle = create_cycle(member, fee_type, %{cycle_start: ~D[2023-01-01], status: :unpaid}) {:ok, view, _html} = live(conn, "/members/#{member.id}") # Mark as paid view |> element("button[phx-click='mark_as_paid'][phx-value-cycle-id='#{cycle.id}']") |> render_click() # Verify cycle is now paid updated_cycle = Ash.read_one!(MembershipFeeCycle |> Ash.Query.filter(id == ^cycle.id)) assert updated_cycle.status == :paid end test "mark as suspended works", %{conn: conn} do fee_type = create_fee_type(%{interval: :yearly}) member = create_member(%{membership_fee_type_id: fee_type.id}) cycle = create_cycle(member, fee_type, %{cycle_start: ~D[2023-01-01], status: :unpaid}) {:ok, view, _html} = live(conn, "/members/#{member.id}") # Mark as suspended view |> element("button[phx-click='mark_as_suspended'][phx-value-cycle-id='#{cycle.id}']") |> render_click() # Verify cycle is now suspended updated_cycle = Ash.read_one!(MembershipFeeCycle |> Ash.Query.filter(id == ^cycle.id)) assert updated_cycle.status == :suspended end test "mark as unpaid works", %{conn: conn} do fee_type = create_fee_type(%{interval: :yearly}) member = create_member(%{membership_fee_type_id: fee_type.id}) cycle = create_cycle(member, fee_type, %{cycle_start: ~D[2023-01-01], status: :paid}) {:ok, view, _html} = live(conn, "/members/#{member.id}") # Mark as unpaid view |> element("button[phx-click='mark_as_unpaid'][phx-value-cycle-id='#{cycle.id}']") |> render_click() # Verify cycle is now unpaid updated_cycle = Ash.read_one!(MembershipFeeCycle |> Ash.Query.filter(id == ^cycle.id)) assert updated_cycle.status == :unpaid end end describe "cycle regeneration" do test "manual regeneration works", %{conn: conn} do fee_type = create_fee_type(%{interval: :yearly}) member = create_member(%{membership_fee_type_id: fee_type.id}) {:ok, view, _html} = live(conn, "/members/#{member.id}") # Trigger regeneration view |> element("button[phx-click='regenerate_cycles']") |> render_click() # Should have cycles generated cycles = MembershipFeeCycle |> Ash.Query.filter(member_id == ^member.id) |> Ash.read!() assert length(cycles) > 0 end end describe "edge cases" do test "handles members without membership fee type gracefully", %{conn: conn} do # No fee type member = create_member(%{}) {:ok, _view, html} = live(conn, "/members/#{member.id}") # Should not crash assert html =~ member.first_name end end end