Replace the create_fee_type/create_cycle helpers duplicated across 18/8 membership-fee test files with a single shared definition in Mv.Fixtures, reconciling the divergent local signatures (including the reversed argument order) into one superset so behavior is unchanged.
226 lines
6.9 KiB
Elixir
226 lines
6.9 KiB
Elixir
defmodule MvWeb.MemberLive.MembershipFeeIntegrationTest do
|
|
@moduledoc """
|
|
Integration tests for membership fee UI workflows.
|
|
"""
|
|
use MvWeb.ConnCase, async: false
|
|
|
|
import Phoenix.LiveViewTest
|
|
import Mv.Fixtures, only: [create_fee_type: 1]
|
|
|
|
alias Mv.MembershipFees.MembershipFeeCycle
|
|
|
|
require Ash.Query
|
|
|
|
# Helper to create a member
|
|
defp create_member(attrs) do
|
|
system_actor = Mv.Helpers.SystemActor.get_system_actor()
|
|
|
|
default_attrs = %{
|
|
first_name: "Test",
|
|
last_name: "Member",
|
|
email: "test.member.#{System.unique_integer([:positive])}@example.com"
|
|
}
|
|
|
|
attrs = Map.merge(default_attrs, attrs)
|
|
{:ok, member} = Mv.Membership.create_member(attrs, actor: system_actor)
|
|
member
|
|
end
|
|
|
|
describe "end-to-end workflows" do
|
|
test "create type → assign to member → view cycles → change status", %{conn: conn} do
|
|
# Create type
|
|
fee_type = create_fee_type(%{name: "Regular", interval: :yearly})
|
|
|
|
# Assign to member
|
|
member = create_member(%{membership_fee_type_id: fee_type.id})
|
|
|
|
# View cycles
|
|
{:ok, view, html} = live(conn, "/members/#{member.id}")
|
|
|
|
assert html =~ "Membership Fees" || html =~ "Mitgliedsbeiträge"
|
|
|
|
# Get a cycle
|
|
cycles =
|
|
MembershipFeeCycle
|
|
|> Ash.Query.filter(member_id == ^member.id)
|
|
|> Ash.read!()
|
|
|
|
if !Enum.empty?(cycles) do
|
|
cycle = List.first(cycles)
|
|
|
|
# Switch to Membership Fees tab
|
|
view
|
|
|> element("button[phx-click='switch_tab'][phx-value-tab='membership_fees']")
|
|
|> render_click()
|
|
|
|
# Change status
|
|
view
|
|
|> element("button[phx-click='mark_cycle_status'][phx-value-cycle_id='#{cycle.id}']")
|
|
|> render_click()
|
|
|
|
# Verify status changed
|
|
system_actor = Mv.Helpers.SystemActor.get_system_actor()
|
|
|
|
updated_cycle =
|
|
Ash.read_one!(MembershipFeeCycle |> Ash.Query.filter(id == ^cycle.id),
|
|
actor: system_actor
|
|
)
|
|
|
|
assert updated_cycle.status == :paid
|
|
end
|
|
end
|
|
|
|
test "change member type → cycles regenerate", %{conn: conn} do
|
|
fee_type1 =
|
|
create_fee_type(%{name: "Type 1", interval: :yearly, amount: Decimal.new("50.00")})
|
|
|
|
fee_type2 =
|
|
create_fee_type(%{name: "Type 2", interval: :yearly, amount: Decimal.new("75.00")})
|
|
|
|
member = create_member(%{membership_fee_type_id: fee_type1.id})
|
|
|
|
# Change type
|
|
{:ok, view, _html} = live(conn, "/members/#{member.id}/edit")
|
|
|
|
view
|
|
|> form("#member-form", %{"member[membership_fee_type_id]" => fee_type2.id})
|
|
|> render_submit()
|
|
|
|
# Verify cycles regenerated with new amount
|
|
cycles =
|
|
MembershipFeeCycle
|
|
|> Ash.Query.filter(member_id == ^member.id)
|
|
|> Ash.Query.filter(status == :unpaid)
|
|
|> Ash.read!()
|
|
|
|
# Future unpaid cycles should have new amount
|
|
Enum.each(cycles, fn cycle ->
|
|
if Date.compare(cycle.cycle_start, Date.utc_today()) != :lt do
|
|
assert Decimal.equal?(cycle.amount, fee_type2.amount)
|
|
end
|
|
end)
|
|
end
|
|
|
|
test "update settings → new members get default type", %{conn: conn} do
|
|
fee_type = create_fee_type(%{interval: :yearly})
|
|
|
|
# Update settings
|
|
system_actor = Mv.Helpers.SystemActor.get_system_actor()
|
|
{:ok, settings} = Mv.Membership.get_settings()
|
|
|
|
settings
|
|
|> Ash.Changeset.for_update(:update_membership_fee_settings, %{
|
|
default_membership_fee_type_id: fee_type.id
|
|
})
|
|
|> Ash.update!(actor: system_actor)
|
|
|
|
# Create new member
|
|
{:ok, view, _html} = live(conn, "/members/new")
|
|
|
|
form_data = %{
|
|
"member[first_name]" => "New",
|
|
"member[last_name]" => "Member",
|
|
"member[email]" => "new#{System.unique_integer([:positive])}@example.com"
|
|
}
|
|
|
|
{:error, {:live_redirect, %{to: _to}}} =
|
|
view
|
|
|> form("#member-form", form_data)
|
|
|> render_submit()
|
|
|
|
# Verify member got default type
|
|
system_actor = Mv.Helpers.SystemActor.get_system_actor()
|
|
|
|
member =
|
|
Mv.Membership.Member
|
|
|> Ash.Query.filter(email == ^form_data["member[email]"])
|
|
|> Ash.read_one!(actor: system_actor)
|
|
|
|
assert member.membership_fee_type_id == fee_type.id
|
|
end
|
|
|
|
test "delete cycle → confirmation → cycle deleted", %{conn: conn} do
|
|
fee_type = create_fee_type(%{interval: :yearly})
|
|
member = create_member(%{membership_fee_type_id: fee_type.id})
|
|
|
|
system_actor = Mv.Helpers.SystemActor.get_system_actor()
|
|
|
|
cycle =
|
|
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: :unpaid
|
|
})
|
|
|> Ash.create!(actor: system_actor)
|
|
|
|
{:ok, view, _html} = live(conn, "/members/#{member.id}")
|
|
|
|
# Switch to Membership Fees tab
|
|
view
|
|
|> element("button[phx-click='switch_tab'][phx-value-tab='membership_fees']")
|
|
|> render_click()
|
|
|
|
# Delete cycle with confirmation
|
|
view
|
|
|> element("button[phx-click='delete_cycle'][phx-value-cycle_id='#{cycle.id}']")
|
|
|> render_click()
|
|
|
|
# Confirm deletion
|
|
view
|
|
|> element("button[phx-click='confirm_delete_cycle'][phx-value-cycle_id='#{cycle.id}']")
|
|
|> render_click()
|
|
|
|
# Verify cycle deleted - Ash.read_one returns {:ok, nil} if not found
|
|
result = MembershipFeeCycle |> Ash.Query.filter(id == ^cycle.id) |> Ash.read_one()
|
|
assert result == {:ok, nil}
|
|
end
|
|
|
|
test "edit cycle amount → modal → amount updated", %{conn: conn} do
|
|
fee_type = create_fee_type(%{interval: :yearly})
|
|
member = create_member(%{membership_fee_type_id: fee_type.id})
|
|
|
|
system_actor = Mv.Helpers.SystemActor.get_system_actor()
|
|
|
|
cycle =
|
|
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: :unpaid
|
|
})
|
|
|> Ash.create!(actor: system_actor)
|
|
|
|
{:ok, view, _html} = live(conn, "/members/#{member.id}")
|
|
|
|
# Switch to Membership Fees tab
|
|
view
|
|
|> element("button[phx-click='switch_tab'][phx-value-tab='membership_fees']")
|
|
|> render_click()
|
|
|
|
# Open edit modal by clicking on the amount span
|
|
view
|
|
|> element("span[phx-click='edit_cycle_amount'][phx-value-cycle_id='#{cycle.id}']")
|
|
|> render_click()
|
|
|
|
# Update amount
|
|
view
|
|
|> form("form[phx-submit='save_cycle_amount']", %{"amount" => "75.00"})
|
|
|> render_submit()
|
|
|
|
# Verify amount updated
|
|
system_actor = Mv.Helpers.SystemActor.get_system_actor()
|
|
|
|
updated_cycle =
|
|
Ash.read_one!(MembershipFeeCycle |> Ash.Query.filter(id == ^cycle.id),
|
|
actor: system_actor
|
|
)
|
|
|
|
assert updated_cycle.amount == Decimal.new("75.00")
|
|
end
|
|
end
|
|
end
|