Add actor parameter to all tests requiring authorization
This commit adds actor: system_actor to all Ash operations in tests that require authorization.
This commit is contained in:
parent
686f69c9e9
commit
0f48a9b15a
75 changed files with 4686 additions and 2859 deletions
|
|
@ -6,13 +6,18 @@ defmodule Mv.MembershipFees.Changes.SetMembershipFeeStartDateTest do
|
|||
|
||||
alias Mv.MembershipFees.Changes.SetMembershipFeeStartDate
|
||||
|
||||
setup do
|
||||
system_actor = Mv.Helpers.SystemActor.get_system_actor()
|
||||
%{actor: system_actor}
|
||||
end
|
||||
|
||||
# Helper to set up settings with specific include_joining_cycle value
|
||||
defp setup_settings(include_joining_cycle) do
|
||||
defp setup_settings(include_joining_cycle, actor) do
|
||||
{:ok, settings} = Mv.Membership.get_settings()
|
||||
|
||||
settings
|
||||
|> Ash.Changeset.for_update(:update, %{include_joining_cycle: include_joining_cycle})
|
||||
|> Ash.update!()
|
||||
|> Ash.update!(actor: actor)
|
||||
end
|
||||
|
||||
describe "calculate_start_date/3" do
|
||||
|
|
@ -127,8 +132,8 @@ defmodule Mv.MembershipFees.Changes.SetMembershipFeeStartDateTest do
|
|||
end
|
||||
|
||||
describe "change/3 integration" do
|
||||
test "sets membership_fee_start_date automatically on member creation" do
|
||||
setup_settings(true)
|
||||
test "sets membership_fee_start_date automatically on member creation", %{actor: actor} do
|
||||
setup_settings(true, actor)
|
||||
|
||||
# Create a fee type
|
||||
fee_type =
|
||||
|
|
@ -138,7 +143,7 @@ defmodule Mv.MembershipFees.Changes.SetMembershipFeeStartDateTest do
|
|||
amount: Decimal.new("50.00"),
|
||||
interval: :yearly
|
||||
})
|
||||
|> Ash.create!()
|
||||
|> Ash.create!(actor: actor)
|
||||
|
||||
# Create member with join_date and fee type but no explicit start date
|
||||
member =
|
||||
|
|
@ -150,14 +155,14 @@ defmodule Mv.MembershipFees.Changes.SetMembershipFeeStartDateTest do
|
|||
join_date: ~D[2024-03-15],
|
||||
membership_fee_type_id: fee_type.id
|
||||
})
|
||||
|> Ash.create!()
|
||||
|> Ash.create!(actor: actor)
|
||||
|
||||
# Should have auto-calculated start date (2024-01-01 for yearly with include_joining_cycle=true)
|
||||
assert member.membership_fee_start_date == ~D[2024-01-01]
|
||||
end
|
||||
|
||||
test "does not override manually set membership_fee_start_date" do
|
||||
setup_settings(true)
|
||||
test "does not override manually set membership_fee_start_date", %{actor: actor} do
|
||||
setup_settings(true, actor)
|
||||
|
||||
# Create a fee type
|
||||
fee_type =
|
||||
|
|
@ -167,7 +172,7 @@ defmodule Mv.MembershipFees.Changes.SetMembershipFeeStartDateTest do
|
|||
amount: Decimal.new("50.00"),
|
||||
interval: :yearly
|
||||
})
|
||||
|> Ash.create!()
|
||||
|> Ash.create!(actor: actor)
|
||||
|
||||
# Create member with explicit start date
|
||||
manual_start_date = ~D[2024-07-01]
|
||||
|
|
@ -182,14 +187,14 @@ defmodule Mv.MembershipFees.Changes.SetMembershipFeeStartDateTest do
|
|||
membership_fee_type_id: fee_type.id,
|
||||
membership_fee_start_date: manual_start_date
|
||||
})
|
||||
|> Ash.create!()
|
||||
|> Ash.create!(actor: actor)
|
||||
|
||||
# Should keep the manually set date
|
||||
assert member.membership_fee_start_date == manual_start_date
|
||||
end
|
||||
|
||||
test "respects include_joining_cycle = false setting" do
|
||||
setup_settings(false)
|
||||
test "respects include_joining_cycle = false setting", %{actor: actor} do
|
||||
setup_settings(false, actor)
|
||||
|
||||
# Create a fee type
|
||||
fee_type =
|
||||
|
|
@ -199,7 +204,7 @@ defmodule Mv.MembershipFees.Changes.SetMembershipFeeStartDateTest do
|
|||
amount: Decimal.new("50.00"),
|
||||
interval: :yearly
|
||||
})
|
||||
|> Ash.create!()
|
||||
|> Ash.create!(actor: actor)
|
||||
|
||||
# Create member
|
||||
member =
|
||||
|
|
@ -211,14 +216,14 @@ defmodule Mv.MembershipFees.Changes.SetMembershipFeeStartDateTest do
|
|||
join_date: ~D[2024-03-15],
|
||||
membership_fee_type_id: fee_type.id
|
||||
})
|
||||
|> Ash.create!()
|
||||
|> Ash.create!(actor: actor)
|
||||
|
||||
# Should have next cycle start date (2025-01-01 for yearly with include_joining_cycle=false)
|
||||
assert member.membership_fee_start_date == ~D[2025-01-01]
|
||||
end
|
||||
|
||||
test "does not set start date without join_date" do
|
||||
setup_settings(true)
|
||||
test "does not set start date without join_date", %{actor: actor} do
|
||||
setup_settings(true, actor)
|
||||
|
||||
# Create a fee type
|
||||
fee_type =
|
||||
|
|
@ -228,7 +233,7 @@ defmodule Mv.MembershipFees.Changes.SetMembershipFeeStartDateTest do
|
|||
amount: Decimal.new("50.00"),
|
||||
interval: :yearly
|
||||
})
|
||||
|> Ash.create!()
|
||||
|> Ash.create!(actor: actor)
|
||||
|
||||
# Create member without join_date
|
||||
member =
|
||||
|
|
@ -240,14 +245,14 @@ defmodule Mv.MembershipFees.Changes.SetMembershipFeeStartDateTest do
|
|||
membership_fee_type_id: fee_type.id
|
||||
# No join_date
|
||||
})
|
||||
|> Ash.create!()
|
||||
|> Ash.create!(actor: actor)
|
||||
|
||||
# Should not have auto-calculated start date
|
||||
assert is_nil(member.membership_fee_start_date)
|
||||
end
|
||||
|
||||
test "does not set start date without membership_fee_type_id" do
|
||||
setup_settings(true)
|
||||
test "does not set start date without membership_fee_type_id", %{actor: actor} do
|
||||
setup_settings(true, actor)
|
||||
|
||||
# Create member without fee type
|
||||
member =
|
||||
|
|
@ -259,7 +264,7 @@ defmodule Mv.MembershipFees.Changes.SetMembershipFeeStartDateTest do
|
|||
join_date: ~D[2024-03-15]
|
||||
# No membership_fee_type_id
|
||||
})
|
||||
|> Ash.create!()
|
||||
|> Ash.create!(actor: actor)
|
||||
|
||||
# Should not have auto-calculated start date
|
||||
assert is_nil(member.membership_fee_start_date)
|
||||
|
|
|
|||
|
|
@ -8,8 +8,13 @@ defmodule Mv.MembershipFees.Changes.ValidateSameIntervalTest do
|
|||
alias Mv.MembershipFees.MembershipFeeType
|
||||
alias Mv.MembershipFees.Changes.ValidateSameInterval
|
||||
|
||||
setup do
|
||||
system_actor = Mv.Helpers.SystemActor.get_system_actor()
|
||||
%{actor: system_actor}
|
||||
end
|
||||
|
||||
# Helper to create a membership fee type
|
||||
defp create_fee_type(attrs) do
|
||||
defp create_fee_type(attrs, actor) do
|
||||
default_attrs = %{
|
||||
name: "Test Fee Type #{System.unique_integer([:positive])}",
|
||||
amount: Decimal.new("50.00"),
|
||||
|
|
@ -20,11 +25,11 @@ defmodule Mv.MembershipFees.Changes.ValidateSameIntervalTest do
|
|||
|
||||
MembershipFeeType
|
||||
|> Ash.Changeset.for_create(:create, attrs)
|
||||
|> Ash.create!()
|
||||
|> Ash.create!(actor: actor)
|
||||
end
|
||||
|
||||
# Helper to create a member
|
||||
defp create_member(attrs) do
|
||||
defp create_member(attrs, actor) do
|
||||
default_attrs = %{
|
||||
first_name: "Test",
|
||||
last_name: "Member",
|
||||
|
|
@ -35,15 +40,15 @@ defmodule Mv.MembershipFees.Changes.ValidateSameIntervalTest do
|
|||
|
||||
Member
|
||||
|> Ash.Changeset.for_create(:create_member, attrs)
|
||||
|> Ash.create!()
|
||||
|> Ash.create!(actor: actor)
|
||||
end
|
||||
|
||||
describe "validate_interval_match/1" do
|
||||
test "allows change to type with same interval" do
|
||||
yearly_type1 = create_fee_type(%{interval: :yearly, name: "Yearly Type 1"})
|
||||
yearly_type2 = create_fee_type(%{interval: :yearly, name: "Yearly Type 2"})
|
||||
test "allows change to type with same interval", %{actor: actor} do
|
||||
yearly_type1 = create_fee_type(%{interval: :yearly, name: "Yearly Type 1"}, actor)
|
||||
yearly_type2 = create_fee_type(%{interval: :yearly, name: "Yearly Type 2"}, actor)
|
||||
|
||||
member = create_member(%{membership_fee_type_id: yearly_type1.id})
|
||||
member = create_member(%{membership_fee_type_id: yearly_type1.id}, actor)
|
||||
|
||||
changeset =
|
||||
member
|
||||
|
|
@ -55,11 +60,11 @@ defmodule Mv.MembershipFees.Changes.ValidateSameIntervalTest do
|
|||
assert changeset.valid?
|
||||
end
|
||||
|
||||
test "prevents change to type with different interval" do
|
||||
yearly_type = create_fee_type(%{interval: :yearly})
|
||||
monthly_type = create_fee_type(%{interval: :monthly})
|
||||
test "prevents change to type with different interval", %{actor: actor} do
|
||||
yearly_type = create_fee_type(%{interval: :yearly}, actor)
|
||||
monthly_type = create_fee_type(%{interval: :monthly}, actor)
|
||||
|
||||
member = create_member(%{membership_fee_type_id: yearly_type.id})
|
||||
member = create_member(%{membership_fee_type_id: yearly_type.id}, actor)
|
||||
|
||||
changeset =
|
||||
member
|
||||
|
|
@ -78,10 +83,10 @@ defmodule Mv.MembershipFees.Changes.ValidateSameIntervalTest do
|
|||
end)
|
||||
end
|
||||
|
||||
test "allows first assignment of membership fee type" do
|
||||
yearly_type = create_fee_type(%{interval: :yearly})
|
||||
test "allows first assignment of membership fee type", %{actor: actor} do
|
||||
yearly_type = create_fee_type(%{interval: :yearly}, actor)
|
||||
# No fee type assigned
|
||||
member = create_member(%{})
|
||||
member = create_member(%{}, actor)
|
||||
|
||||
changeset =
|
||||
member
|
||||
|
|
@ -93,9 +98,9 @@ defmodule Mv.MembershipFees.Changes.ValidateSameIntervalTest do
|
|||
assert changeset.valid?
|
||||
end
|
||||
|
||||
test "prevents removal of membership fee type" do
|
||||
yearly_type = create_fee_type(%{interval: :yearly})
|
||||
member = create_member(%{membership_fee_type_id: yearly_type.id})
|
||||
test "prevents removal of membership fee type", %{actor: actor} do
|
||||
yearly_type = create_fee_type(%{interval: :yearly}, actor)
|
||||
member = create_member(%{membership_fee_type_id: yearly_type.id}, actor)
|
||||
|
||||
changeset =
|
||||
member
|
||||
|
|
@ -113,9 +118,9 @@ defmodule Mv.MembershipFees.Changes.ValidateSameIntervalTest do
|
|||
end)
|
||||
end
|
||||
|
||||
test "does nothing when membership_fee_type_id is not changed" do
|
||||
yearly_type = create_fee_type(%{interval: :yearly})
|
||||
member = create_member(%{membership_fee_type_id: yearly_type.id})
|
||||
test "does nothing when membership_fee_type_id is not changed", %{actor: actor} do
|
||||
yearly_type = create_fee_type(%{interval: :yearly}, actor)
|
||||
member = create_member(%{membership_fee_type_id: yearly_type.id}, actor)
|
||||
|
||||
changeset =
|
||||
member
|
||||
|
|
@ -127,11 +132,11 @@ defmodule Mv.MembershipFees.Changes.ValidateSameIntervalTest do
|
|||
assert changeset.valid?
|
||||
end
|
||||
|
||||
test "error message is clear and helpful" do
|
||||
yearly_type = create_fee_type(%{interval: :yearly})
|
||||
quarterly_type = create_fee_type(%{interval: :quarterly})
|
||||
test "error message is clear and helpful", %{actor: actor} do
|
||||
yearly_type = create_fee_type(%{interval: :yearly}, actor)
|
||||
quarterly_type = create_fee_type(%{interval: :quarterly}, actor)
|
||||
|
||||
member = create_member(%{membership_fee_type_id: yearly_type.id})
|
||||
member = create_member(%{membership_fee_type_id: yearly_type.id}, actor)
|
||||
|
||||
changeset =
|
||||
member
|
||||
|
|
@ -146,25 +151,31 @@ defmodule Mv.MembershipFees.Changes.ValidateSameIntervalTest do
|
|||
assert error.message =~ "same-interval"
|
||||
end
|
||||
|
||||
test "handles all interval types correctly" do
|
||||
test "handles all interval types correctly", %{actor: actor} do
|
||||
intervals = [:monthly, :quarterly, :half_yearly, :yearly]
|
||||
|
||||
for interval1 <- intervals,
|
||||
interval2 <- intervals,
|
||||
interval1 != interval2 do
|
||||
type1 =
|
||||
create_fee_type(%{
|
||||
interval: interval1,
|
||||
name: "Type #{interval1} #{System.unique_integer([:positive])}"
|
||||
})
|
||||
create_fee_type(
|
||||
%{
|
||||
interval: interval1,
|
||||
name: "Type #{interval1} #{System.unique_integer([:positive])}"
|
||||
},
|
||||
actor
|
||||
)
|
||||
|
||||
type2 =
|
||||
create_fee_type(%{
|
||||
interval: interval2,
|
||||
name: "Type #{interval2} #{System.unique_integer([:positive])}"
|
||||
})
|
||||
create_fee_type(
|
||||
%{
|
||||
interval: interval2,
|
||||
name: "Type #{interval2} #{System.unique_integer([:positive])}"
|
||||
},
|
||||
actor
|
||||
)
|
||||
|
||||
member = create_member(%{membership_fee_type_id: type1.id})
|
||||
member = create_member(%{membership_fee_type_id: type1.id}, actor)
|
||||
|
||||
changeset =
|
||||
member
|
||||
|
|
@ -180,11 +191,11 @@ defmodule Mv.MembershipFees.Changes.ValidateSameIntervalTest do
|
|||
end
|
||||
|
||||
describe "integration with update_member action" do
|
||||
test "validation works when updating member via update_member action" do
|
||||
yearly_type = create_fee_type(%{interval: :yearly})
|
||||
monthly_type = create_fee_type(%{interval: :monthly})
|
||||
test "validation works when updating member via update_member action", %{actor: actor} do
|
||||
yearly_type = create_fee_type(%{interval: :yearly}, actor)
|
||||
monthly_type = create_fee_type(%{interval: :monthly}, actor)
|
||||
|
||||
member = create_member(%{membership_fee_type_id: yearly_type.id})
|
||||
member = create_member(%{membership_fee_type_id: yearly_type.id}, actor)
|
||||
|
||||
# Try to update member with different interval type
|
||||
assert {:error, %Ash.Error.Invalid{} = error} =
|
||||
|
|
@ -192,7 +203,7 @@ defmodule Mv.MembershipFees.Changes.ValidateSameIntervalTest do
|
|||
|> Ash.Changeset.for_update(:update_member, %{
|
||||
membership_fee_type_id: monthly_type.id
|
||||
})
|
||||
|> Ash.update()
|
||||
|> Ash.update(actor: actor)
|
||||
|
||||
# Check that error is about interval mismatch
|
||||
error_message = extract_error_message(error)
|
||||
|
|
@ -201,11 +212,11 @@ defmodule Mv.MembershipFees.Changes.ValidateSameIntervalTest do
|
|||
assert error_message =~ "same-interval"
|
||||
end
|
||||
|
||||
test "allows update when interval matches" do
|
||||
yearly_type1 = create_fee_type(%{interval: :yearly, name: "Yearly Type 1"})
|
||||
yearly_type2 = create_fee_type(%{interval: :yearly, name: "Yearly Type 2"})
|
||||
test "allows update when interval matches", %{actor: actor} do
|
||||
yearly_type1 = create_fee_type(%{interval: :yearly, name: "Yearly Type 1"}, actor)
|
||||
yearly_type2 = create_fee_type(%{interval: :yearly, name: "Yearly Type 2"}, actor)
|
||||
|
||||
member = create_member(%{membership_fee_type_id: yearly_type1.id})
|
||||
member = create_member(%{membership_fee_type_id: yearly_type1.id}, actor)
|
||||
|
||||
# Update member with same-interval type
|
||||
assert {:ok, updated_member} =
|
||||
|
|
@ -213,7 +224,7 @@ defmodule Mv.MembershipFees.Changes.ValidateSameIntervalTest do
|
|||
|> Ash.Changeset.for_update(:update_member, %{
|
||||
membership_fee_type_id: yearly_type2.id
|
||||
})
|
||||
|> Ash.update()
|
||||
|> Ash.update(actor: actor)
|
||||
|
||||
assert updated_member.membership_fee_type_id == yearly_type2.id
|
||||
end
|
||||
|
|
|
|||
|
|
@ -8,211 +8,287 @@ defmodule Mv.MembershipFees.ForeignKeyTest do
|
|||
alias Mv.MembershipFees.MembershipFeeType
|
||||
alias Mv.Membership.Member
|
||||
|
||||
setup do
|
||||
system_actor = Mv.Helpers.SystemActor.get_system_actor()
|
||||
%{actor: system_actor}
|
||||
end
|
||||
|
||||
describe "CASCADE behavior" do
|
||||
test "deleting member deletes associated membership_fee_cycles" do
|
||||
test "deleting member deletes associated membership_fee_cycles", %{actor: actor} do
|
||||
# Create member
|
||||
{:ok, member} =
|
||||
Ash.create(Member, %{
|
||||
first_name: "Cascade",
|
||||
last_name: "Test",
|
||||
email: "cascade.test.#{System.unique_integer([:positive])}@example.com"
|
||||
})
|
||||
Ash.create(
|
||||
Member,
|
||||
%{
|
||||
first_name: "Cascade",
|
||||
last_name: "Test",
|
||||
email: "cascade.test.#{System.unique_integer([:positive])}@example.com"
|
||||
},
|
||||
actor: actor
|
||||
)
|
||||
|
||||
# Create fee type
|
||||
{:ok, fee_type} =
|
||||
Ash.create(MembershipFeeType, %{
|
||||
name: "Cascade Test Fee #{System.unique_integer([:positive])}",
|
||||
amount: Decimal.new("100.00"),
|
||||
interval: :monthly
|
||||
})
|
||||
Ash.create(
|
||||
MembershipFeeType,
|
||||
%{
|
||||
name: "Cascade Test Fee #{System.unique_integer([:positive])}",
|
||||
amount: Decimal.new("100.00"),
|
||||
interval: :monthly
|
||||
},
|
||||
actor: actor
|
||||
)
|
||||
|
||||
# Create multiple cycles for this member
|
||||
{:ok, cycle1} =
|
||||
Ash.create(MembershipFeeCycle, %{
|
||||
cycle_start: ~D[2025-01-01],
|
||||
amount: Decimal.new("100.00"),
|
||||
member_id: member.id,
|
||||
membership_fee_type_id: fee_type.id
|
||||
})
|
||||
Ash.create(
|
||||
MembershipFeeCycle,
|
||||
%{
|
||||
cycle_start: ~D[2025-01-01],
|
||||
amount: Decimal.new("100.00"),
|
||||
member_id: member.id,
|
||||
membership_fee_type_id: fee_type.id
|
||||
},
|
||||
actor: actor
|
||||
)
|
||||
|
||||
{:ok, cycle2} =
|
||||
Ash.create(MembershipFeeCycle, %{
|
||||
cycle_start: ~D[2025-02-01],
|
||||
amount: Decimal.new("100.00"),
|
||||
member_id: member.id,
|
||||
membership_fee_type_id: fee_type.id
|
||||
})
|
||||
Ash.create(
|
||||
MembershipFeeCycle,
|
||||
%{
|
||||
cycle_start: ~D[2025-02-01],
|
||||
amount: Decimal.new("100.00"),
|
||||
member_id: member.id,
|
||||
membership_fee_type_id: fee_type.id
|
||||
},
|
||||
actor: actor
|
||||
)
|
||||
|
||||
# Verify cycles exist
|
||||
assert {:ok, _} = Ash.get(MembershipFeeCycle, cycle1.id)
|
||||
assert {:ok, _} = Ash.get(MembershipFeeCycle, cycle2.id)
|
||||
assert {:ok, _} = Ash.get(MembershipFeeCycle, cycle1.id, actor: actor)
|
||||
assert {:ok, _} = Ash.get(MembershipFeeCycle, cycle2.id, actor: actor)
|
||||
|
||||
# Delete member
|
||||
assert :ok = Ash.destroy(member)
|
||||
assert :ok = Ash.destroy(member, actor: actor)
|
||||
|
||||
# Verify cycles are also deleted (CASCADE)
|
||||
# NotFound is wrapped in Ash.Error.Invalid
|
||||
assert {:error, %Ash.Error.Invalid{}} = Ash.get(MembershipFeeCycle, cycle1.id)
|
||||
assert {:error, %Ash.Error.Invalid{}} = Ash.get(MembershipFeeCycle, cycle2.id)
|
||||
assert {:error, %Ash.Error.Invalid{}} = Ash.get(MembershipFeeCycle, cycle1.id, actor: actor)
|
||||
assert {:error, %Ash.Error.Invalid{}} = Ash.get(MembershipFeeCycle, cycle2.id, actor: actor)
|
||||
end
|
||||
end
|
||||
|
||||
describe "RESTRICT behavior" do
|
||||
test "cannot delete membership_fee_type if cycles reference it" do
|
||||
test "cannot delete membership_fee_type if cycles reference it", %{actor: actor} do
|
||||
# Create member
|
||||
{:ok, member} =
|
||||
Ash.create(Member, %{
|
||||
first_name: "Restrict",
|
||||
last_name: "Test",
|
||||
email: "restrict.test.#{System.unique_integer([:positive])}@example.com"
|
||||
})
|
||||
Ash.create(
|
||||
Member,
|
||||
%{
|
||||
first_name: "Restrict",
|
||||
last_name: "Test",
|
||||
email: "restrict.test.#{System.unique_integer([:positive])}@example.com"
|
||||
},
|
||||
actor: actor
|
||||
)
|
||||
|
||||
# Create fee type
|
||||
{:ok, fee_type} =
|
||||
Ash.create(MembershipFeeType, %{
|
||||
name: "Restrict Test Fee #{System.unique_integer([:positive])}",
|
||||
amount: Decimal.new("100.00"),
|
||||
interval: :monthly
|
||||
})
|
||||
Ash.create(
|
||||
MembershipFeeType,
|
||||
%{
|
||||
name: "Restrict Test Fee #{System.unique_integer([:positive])}",
|
||||
amount: Decimal.new("100.00"),
|
||||
interval: :monthly
|
||||
},
|
||||
actor: actor
|
||||
)
|
||||
|
||||
# Create a cycle referencing this fee type
|
||||
{:ok, _cycle} =
|
||||
Ash.create(MembershipFeeCycle, %{
|
||||
cycle_start: ~D[2025-01-01],
|
||||
amount: Decimal.new("100.00"),
|
||||
member_id: member.id,
|
||||
membership_fee_type_id: fee_type.id
|
||||
})
|
||||
Ash.create(
|
||||
MembershipFeeCycle,
|
||||
%{
|
||||
cycle_start: ~D[2025-01-01],
|
||||
amount: Decimal.new("100.00"),
|
||||
member_id: member.id,
|
||||
membership_fee_type_id: fee_type.id
|
||||
},
|
||||
actor: actor
|
||||
)
|
||||
|
||||
# Try to delete fee type - should fail due to RESTRICT
|
||||
assert {:error, error} = Ash.destroy(fee_type)
|
||||
assert {:error, error} = Ash.destroy(fee_type, actor: actor)
|
||||
|
||||
# Check that it's a foreign key violation error
|
||||
assert is_struct(error, Ash.Error.Invalid) or is_struct(error, Ash.Error.Unknown)
|
||||
end
|
||||
|
||||
test "can delete membership_fee_type if no cycles reference it" do
|
||||
test "can delete membership_fee_type if no cycles reference it", %{actor: actor} do
|
||||
# Create fee type without any cycles
|
||||
{:ok, fee_type} =
|
||||
Ash.create(MembershipFeeType, %{
|
||||
name: "Deletable Fee #{System.unique_integer([:positive])}",
|
||||
amount: Decimal.new("100.00"),
|
||||
interval: :monthly
|
||||
})
|
||||
Ash.create(
|
||||
MembershipFeeType,
|
||||
%{
|
||||
name: "Deletable Fee #{System.unique_integer([:positive])}",
|
||||
amount: Decimal.new("100.00"),
|
||||
interval: :monthly
|
||||
},
|
||||
actor: actor
|
||||
)
|
||||
|
||||
# Should be able to delete
|
||||
assert :ok = Ash.destroy(fee_type)
|
||||
assert :ok = Ash.destroy(fee_type, actor: actor)
|
||||
|
||||
# Verify it's gone (NotFound is wrapped in Ash.Error.Invalid)
|
||||
assert {:error, %Ash.Error.Invalid{}} = Ash.get(MembershipFeeType, fee_type.id)
|
||||
assert {:error, %Ash.Error.Invalid{}} =
|
||||
Ash.get(MembershipFeeType, fee_type.id, actor: actor)
|
||||
end
|
||||
|
||||
test "cannot delete membership_fee_type if members reference it" do
|
||||
test "cannot delete membership_fee_type if members reference it", %{actor: actor} do
|
||||
# Create fee type
|
||||
{:ok, fee_type} =
|
||||
Ash.create(MembershipFeeType, %{
|
||||
name: "Member Ref Fee #{System.unique_integer([:positive])}",
|
||||
amount: Decimal.new("100.00"),
|
||||
interval: :monthly
|
||||
})
|
||||
Ash.create(
|
||||
MembershipFeeType,
|
||||
%{
|
||||
name: "Member Ref Fee #{System.unique_integer([:positive])}",
|
||||
amount: Decimal.new("100.00"),
|
||||
interval: :monthly
|
||||
},
|
||||
actor: actor
|
||||
)
|
||||
|
||||
# Create member with this fee type
|
||||
{:ok, _member} =
|
||||
Ash.create(Member, %{
|
||||
first_name: "FeeType",
|
||||
last_name: "Reference",
|
||||
email: "feetype.ref.#{System.unique_integer([:positive])}@example.com",
|
||||
membership_fee_type_id: fee_type.id
|
||||
})
|
||||
Ash.create(
|
||||
Member,
|
||||
%{
|
||||
first_name: "FeeType",
|
||||
last_name: "Reference",
|
||||
email: "feetype.ref.#{System.unique_integer([:positive])}@example.com",
|
||||
membership_fee_type_id: fee_type.id
|
||||
},
|
||||
actor: actor
|
||||
)
|
||||
|
||||
# Try to delete fee type - should fail due to RESTRICT
|
||||
assert {:error, error} = Ash.destroy(fee_type)
|
||||
assert {:error, error} = Ash.destroy(fee_type, actor: actor)
|
||||
assert is_struct(error, Ash.Error.Invalid) or is_struct(error, Ash.Error.Unknown)
|
||||
end
|
||||
end
|
||||
|
||||
describe "member extensions" do
|
||||
test "member can be created with membership_fee_type_id" do
|
||||
test "member can be created with membership_fee_type_id", %{actor: actor} do
|
||||
# Create fee type first
|
||||
{:ok, fee_type} =
|
||||
Ash.create(MembershipFeeType, %{
|
||||
name: "Create Test Fee #{System.unique_integer([:positive])}",
|
||||
amount: Decimal.new("100.00"),
|
||||
interval: :yearly
|
||||
})
|
||||
Ash.create(
|
||||
MembershipFeeType,
|
||||
%{
|
||||
name: "Create Test Fee #{System.unique_integer([:positive])}",
|
||||
amount: Decimal.new("100.00"),
|
||||
interval: :yearly
|
||||
},
|
||||
actor: actor
|
||||
)
|
||||
|
||||
# Create member with fee type
|
||||
{:ok, member} =
|
||||
Ash.create(Member, %{
|
||||
first_name: "With",
|
||||
last_name: "FeeType",
|
||||
email: "with.feetype.#{System.unique_integer([:positive])}@example.com",
|
||||
membership_fee_type_id: fee_type.id
|
||||
})
|
||||
Ash.create(
|
||||
Member,
|
||||
%{
|
||||
first_name: "With",
|
||||
last_name: "FeeType",
|
||||
email: "with.feetype.#{System.unique_integer([:positive])}@example.com",
|
||||
membership_fee_type_id: fee_type.id
|
||||
},
|
||||
actor: actor
|
||||
)
|
||||
|
||||
assert member.membership_fee_type_id == fee_type.id
|
||||
end
|
||||
|
||||
test "member can be created with membership_fee_start_date" do
|
||||
test "member can be created with membership_fee_start_date", %{actor: actor} do
|
||||
{:ok, member} =
|
||||
Ash.create(Member, %{
|
||||
first_name: "With",
|
||||
last_name: "StartDate",
|
||||
email: "with.startdate.#{System.unique_integer([:positive])}@example.com",
|
||||
membership_fee_start_date: ~D[2025-01-01]
|
||||
})
|
||||
Ash.create(
|
||||
Member,
|
||||
%{
|
||||
first_name: "With",
|
||||
last_name: "StartDate",
|
||||
email: "with.startdate.#{System.unique_integer([:positive])}@example.com",
|
||||
membership_fee_start_date: ~D[2025-01-01]
|
||||
},
|
||||
actor: actor
|
||||
)
|
||||
|
||||
assert member.membership_fee_start_date == ~D[2025-01-01]
|
||||
end
|
||||
|
||||
test "member can be created without membership fee fields" do
|
||||
test "member can be created without membership fee fields", %{actor: actor} do
|
||||
{:ok, member} =
|
||||
Ash.create(Member, %{
|
||||
first_name: "No",
|
||||
last_name: "FeeFields",
|
||||
email: "no.feefields.#{System.unique_integer([:positive])}@example.com"
|
||||
})
|
||||
Ash.create(
|
||||
Member,
|
||||
%{
|
||||
first_name: "No",
|
||||
last_name: "FeeFields",
|
||||
email: "no.feefields.#{System.unique_integer([:positive])}@example.com"
|
||||
},
|
||||
actor: actor
|
||||
)
|
||||
|
||||
assert member.membership_fee_type_id == nil
|
||||
assert member.membership_fee_start_date == nil
|
||||
end
|
||||
|
||||
test "member can be updated with membership_fee_type_id" do
|
||||
test "member can be updated with membership_fee_type_id", %{actor: actor} do
|
||||
# Create fee type
|
||||
{:ok, fee_type} =
|
||||
Ash.create(MembershipFeeType, %{
|
||||
name: "Update Test Fee #{System.unique_integer([:positive])}",
|
||||
amount: Decimal.new("100.00"),
|
||||
interval: :yearly
|
||||
})
|
||||
Ash.create(
|
||||
MembershipFeeType,
|
||||
%{
|
||||
name: "Update Test Fee #{System.unique_integer([:positive])}",
|
||||
amount: Decimal.new("100.00"),
|
||||
interval: :yearly
|
||||
},
|
||||
actor: actor
|
||||
)
|
||||
|
||||
# Create member without fee type
|
||||
{:ok, member} =
|
||||
Ash.create(Member, %{
|
||||
first_name: "Update",
|
||||
last_name: "Test",
|
||||
email: "update.test.#{System.unique_integer([:positive])}@example.com"
|
||||
})
|
||||
Ash.create(
|
||||
Member,
|
||||
%{
|
||||
first_name: "Update",
|
||||
last_name: "Test",
|
||||
email: "update.test.#{System.unique_integer([:positive])}@example.com"
|
||||
},
|
||||
actor: actor
|
||||
)
|
||||
|
||||
assert member.membership_fee_type_id == nil
|
||||
|
||||
# Update member with fee type
|
||||
{:ok, updated_member} = Ash.update(member, %{membership_fee_type_id: fee_type.id})
|
||||
{:ok, updated_member} =
|
||||
Ash.update(member, %{membership_fee_type_id: fee_type.id}, actor: actor)
|
||||
|
||||
assert updated_member.membership_fee_type_id == fee_type.id
|
||||
end
|
||||
|
||||
test "member can be updated with membership_fee_start_date" do
|
||||
test "member can be updated with membership_fee_start_date", %{actor: actor} do
|
||||
{:ok, member} =
|
||||
Ash.create(Member, %{
|
||||
first_name: "Start",
|
||||
last_name: "Date",
|
||||
email: "start.date.#{System.unique_integer([:positive])}@example.com"
|
||||
})
|
||||
Ash.create(
|
||||
Member,
|
||||
%{
|
||||
first_name: "Start",
|
||||
last_name: "Date",
|
||||
email: "start.date.#{System.unique_integer([:positive])}@example.com"
|
||||
},
|
||||
actor: actor
|
||||
)
|
||||
|
||||
assert member.membership_fee_start_date == nil
|
||||
|
||||
{:ok, updated_member} = Ash.update(member, %{membership_fee_start_date: ~D[2025-06-01]})
|
||||
{:ok, updated_member} =
|
||||
Ash.update(member, %{membership_fee_start_date: ~D[2025-06-01]}, actor: actor)
|
||||
|
||||
assert updated_member.membership_fee_start_date == ~D[2025-06-01]
|
||||
end
|
||||
|
|
|
|||
|
|
@ -10,8 +10,13 @@ defmodule Mv.MembershipFees.MemberCycleIntegrationTest do
|
|||
|
||||
require Ash.Query
|
||||
|
||||
setup do
|
||||
system_actor = Mv.Helpers.SystemActor.get_system_actor()
|
||||
%{actor: system_actor}
|
||||
end
|
||||
|
||||
# Helper to create a membership fee type
|
||||
defp create_fee_type(attrs) do
|
||||
defp create_fee_type(attrs, actor) do
|
||||
default_attrs = %{
|
||||
name: "Test Fee Type #{System.unique_integer([:positive])}",
|
||||
amount: Decimal.new("50.00"),
|
||||
|
|
@ -22,30 +27,30 @@ defmodule Mv.MembershipFees.MemberCycleIntegrationTest do
|
|||
|
||||
MembershipFeeType
|
||||
|> Ash.Changeset.for_create(:create, attrs)
|
||||
|> Ash.create!()
|
||||
|> Ash.create!(actor: actor)
|
||||
end
|
||||
|
||||
# Helper to set up settings
|
||||
defp setup_settings(include_joining_cycle) do
|
||||
defp setup_settings(include_joining_cycle, actor) do
|
||||
{:ok, settings} = Mv.Membership.get_settings()
|
||||
|
||||
settings
|
||||
|> Ash.Changeset.for_update(:update, %{include_joining_cycle: include_joining_cycle})
|
||||
|> Ash.update!()
|
||||
|> Ash.update!(actor: actor)
|
||||
end
|
||||
|
||||
# Helper to get cycles for a member
|
||||
defp get_member_cycles(member_id) do
|
||||
defp get_member_cycles(member_id, actor) do
|
||||
MembershipFeeCycle
|
||||
|> Ash.Query.filter(member_id == ^member_id)
|
||||
|> Ash.Query.sort(cycle_start: :asc)
|
||||
|> Ash.read!()
|
||||
|> Ash.read!(actor: actor)
|
||||
end
|
||||
|
||||
describe "member creation triggers cycle generation" do
|
||||
test "creates cycles when member is created with fee type and join_date" do
|
||||
setup_settings(true)
|
||||
fee_type = create_fee_type(%{interval: :yearly})
|
||||
test "creates cycles when member is created with fee type and join_date", %{actor: actor} do
|
||||
setup_settings(true, actor)
|
||||
fee_type = create_fee_type(%{interval: :yearly}, actor)
|
||||
|
||||
member =
|
||||
Member
|
||||
|
|
@ -56,9 +61,9 @@ defmodule Mv.MembershipFees.MemberCycleIntegrationTest do
|
|||
join_date: ~D[2023-03-15],
|
||||
membership_fee_type_id: fee_type.id
|
||||
})
|
||||
|> Ash.create!()
|
||||
|> Ash.create!(actor: actor)
|
||||
|
||||
cycles = get_member_cycles(member.id)
|
||||
cycles = get_member_cycles(member.id, actor)
|
||||
|
||||
# Should have cycles for 2023 and 2024 (and possibly current year)
|
||||
assert length(cycles) >= 2
|
||||
|
|
@ -72,8 +77,8 @@ defmodule Mv.MembershipFees.MemberCycleIntegrationTest do
|
|||
end)
|
||||
end
|
||||
|
||||
test "does not create cycles when member has no fee type" do
|
||||
setup_settings(true)
|
||||
test "does not create cycles when member has no fee type", %{actor: actor} do
|
||||
setup_settings(true, actor)
|
||||
|
||||
member =
|
||||
Member
|
||||
|
|
@ -84,16 +89,16 @@ defmodule Mv.MembershipFees.MemberCycleIntegrationTest do
|
|||
join_date: ~D[2023-03-15]
|
||||
# No membership_fee_type_id
|
||||
})
|
||||
|> Ash.create!()
|
||||
|> Ash.create!(actor: actor)
|
||||
|
||||
cycles = get_member_cycles(member.id)
|
||||
cycles = get_member_cycles(member.id, actor)
|
||||
|
||||
assert cycles == []
|
||||
end
|
||||
|
||||
test "does not create cycles when member has no join_date" do
|
||||
setup_settings(true)
|
||||
fee_type = create_fee_type(%{interval: :yearly})
|
||||
test "does not create cycles when member has no join_date", %{actor: actor} do
|
||||
setup_settings(true, actor)
|
||||
fee_type = create_fee_type(%{interval: :yearly}, actor)
|
||||
|
||||
member =
|
||||
Member
|
||||
|
|
@ -104,18 +109,18 @@ defmodule Mv.MembershipFees.MemberCycleIntegrationTest do
|
|||
membership_fee_type_id: fee_type.id
|
||||
# No join_date
|
||||
})
|
||||
|> Ash.create!()
|
||||
|> Ash.create!(actor: actor)
|
||||
|
||||
cycles = get_member_cycles(member.id)
|
||||
cycles = get_member_cycles(member.id, actor)
|
||||
|
||||
assert cycles == []
|
||||
end
|
||||
end
|
||||
|
||||
describe "member update triggers cycle generation" do
|
||||
test "generates cycles when fee type is assigned to existing member" do
|
||||
setup_settings(true)
|
||||
fee_type = create_fee_type(%{interval: :yearly})
|
||||
test "generates cycles when fee type is assigned to existing member", %{actor: actor} do
|
||||
setup_settings(true, actor)
|
||||
fee_type = create_fee_type(%{interval: :yearly}, actor)
|
||||
|
||||
# Create member without fee type
|
||||
member =
|
||||
|
|
@ -126,17 +131,17 @@ defmodule Mv.MembershipFees.MemberCycleIntegrationTest do
|
|||
email: "test#{System.unique_integer([:positive])}@example.com",
|
||||
join_date: ~D[2023-03-15]
|
||||
})
|
||||
|> Ash.create!()
|
||||
|> Ash.create!(actor: actor)
|
||||
|
||||
# Verify no cycles yet
|
||||
assert get_member_cycles(member.id) == []
|
||||
assert get_member_cycles(member.id, actor) == []
|
||||
|
||||
# Update to assign fee type
|
||||
member
|
||||
|> Ash.Changeset.for_update(:update_member, %{membership_fee_type_id: fee_type.id})
|
||||
|> Ash.update!()
|
||||
|> Ash.update!(actor: actor)
|
||||
|
||||
cycles = get_member_cycles(member.id)
|
||||
cycles = get_member_cycles(member.id, actor)
|
||||
|
||||
# Should have generated cycles
|
||||
assert length(cycles) >= 2
|
||||
|
|
@ -144,9 +149,9 @@ defmodule Mv.MembershipFees.MemberCycleIntegrationTest do
|
|||
end
|
||||
|
||||
describe "concurrent cycle generation" do
|
||||
test "handles multiple members being created concurrently" do
|
||||
setup_settings(true)
|
||||
fee_type = create_fee_type(%{interval: :yearly})
|
||||
test "handles multiple members being created concurrently", %{actor: actor} do
|
||||
setup_settings(true, actor)
|
||||
fee_type = create_fee_type(%{interval: :yearly}, actor)
|
||||
|
||||
# Create multiple members concurrently
|
||||
tasks =
|
||||
|
|
@ -160,7 +165,7 @@ defmodule Mv.MembershipFees.MemberCycleIntegrationTest do
|
|||
join_date: ~D[2023-03-15],
|
||||
membership_fee_type_id: fee_type.id
|
||||
})
|
||||
|> Ash.create!()
|
||||
|> Ash.create!(actor: actor)
|
||||
end)
|
||||
end)
|
||||
|
||||
|
|
@ -168,16 +173,16 @@ defmodule Mv.MembershipFees.MemberCycleIntegrationTest do
|
|||
|
||||
# Each member should have cycles
|
||||
Enum.each(members, fn member ->
|
||||
cycles = get_member_cycles(member.id)
|
||||
cycles = get_member_cycles(member.id, actor)
|
||||
assert length(cycles) >= 2, "Member #{member.id} should have at least 2 cycles"
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
describe "idempotent cycle generation" do
|
||||
test "running generation multiple times does not create duplicate cycles" do
|
||||
setup_settings(true)
|
||||
fee_type = create_fee_type(%{interval: :yearly})
|
||||
test "running generation multiple times does not create duplicate cycles", %{actor: actor} do
|
||||
setup_settings(true, actor)
|
||||
fee_type = create_fee_type(%{interval: :yearly}, actor)
|
||||
|
||||
member =
|
||||
Member
|
||||
|
|
@ -188,9 +193,9 @@ defmodule Mv.MembershipFees.MemberCycleIntegrationTest do
|
|||
join_date: ~D[2023-03-15],
|
||||
membership_fee_type_id: fee_type.id
|
||||
})
|
||||
|> Ash.create!()
|
||||
|> Ash.create!(actor: actor)
|
||||
|
||||
initial_cycles = get_member_cycles(member.id)
|
||||
initial_cycles = get_member_cycles(member.id, actor)
|
||||
initial_count = length(initial_cycles)
|
||||
|
||||
# Use a fixed "today" date to avoid date dependency
|
||||
|
|
@ -201,7 +206,7 @@ defmodule Mv.MembershipFees.MemberCycleIntegrationTest do
|
|||
{:ok, _, _} =
|
||||
Mv.MembershipFees.CycleGenerator.generate_cycles_for_member(member.id, today: today)
|
||||
|
||||
final_cycles = get_member_cycles(member.id)
|
||||
final_cycles = get_member_cycles(member.id, actor)
|
||||
final_count = length(final_cycles)
|
||||
|
||||
# Should have same number of cycles (idempotent)
|
||||
|
|
|
|||
|
|
@ -8,8 +8,13 @@ defmodule Mv.MembershipFees.MembershipFeeCycleTest do
|
|||
alias Mv.MembershipFees.MembershipFeeType
|
||||
alias Mv.Membership.Member
|
||||
|
||||
setup do
|
||||
system_actor = Mv.Helpers.SystemActor.get_system_actor()
|
||||
%{actor: system_actor}
|
||||
end
|
||||
|
||||
# Helper to create a membership fee type
|
||||
defp create_fee_type(attrs) do
|
||||
defp create_fee_type(attrs, actor) do
|
||||
default_attrs = %{
|
||||
name: "Test Fee Type #{System.unique_integer([:positive])}",
|
||||
amount: Decimal.new("50.00"),
|
||||
|
|
@ -20,11 +25,11 @@ defmodule Mv.MembershipFees.MembershipFeeCycleTest do
|
|||
|
||||
MembershipFeeType
|
||||
|> Ash.Changeset.for_create(:create, attrs)
|
||||
|> Ash.create!()
|
||||
|> Ash.create!(actor: actor)
|
||||
end
|
||||
|
||||
# Helper to create a member
|
||||
defp create_member(attrs) do
|
||||
defp create_member(attrs, actor) do
|
||||
default_attrs = %{
|
||||
first_name: "Test",
|
||||
last_name: "Member",
|
||||
|
|
@ -35,11 +40,11 @@ defmodule Mv.MembershipFees.MembershipFeeCycleTest do
|
|||
|
||||
Member
|
||||
|> Ash.Changeset.for_create(:create_member, attrs)
|
||||
|> Ash.create!()
|
||||
|> Ash.create!(actor: actor)
|
||||
end
|
||||
|
||||
# Helper to create a cycle
|
||||
defp create_cycle(member, fee_type, attrs) do
|
||||
defp create_cycle(member, fee_type, attrs, actor) do
|
||||
default_attrs = %{
|
||||
cycle_start: ~D[2024-01-01],
|
||||
amount: Decimal.new("50.00"),
|
||||
|
|
@ -51,13 +56,13 @@ defmodule Mv.MembershipFees.MembershipFeeCycleTest do
|
|||
|
||||
MembershipFeeCycle
|
||||
|> Ash.Changeset.for_create(:create, attrs)
|
||||
|> Ash.create!()
|
||||
|> Ash.create!(actor: actor)
|
||||
end
|
||||
|
||||
describe "status defaults" do
|
||||
test "status defaults to :unpaid when creating a cycle" do
|
||||
fee_type = create_fee_type(%{interval: :yearly})
|
||||
member = create_member(%{membership_fee_type_id: fee_type.id})
|
||||
test "status defaults to :unpaid when creating a cycle", %{actor: actor} do
|
||||
fee_type = create_fee_type(%{interval: :yearly}, actor)
|
||||
member = create_member(%{membership_fee_type_id: fee_type.id}, actor)
|
||||
|
||||
cycle =
|
||||
MembershipFeeCycle
|
||||
|
|
@ -67,29 +72,30 @@ defmodule Mv.MembershipFees.MembershipFeeCycleTest do
|
|||
member_id: member.id,
|
||||
membership_fee_type_id: fee_type.id
|
||||
})
|
||||
|> Ash.create!()
|
||||
|> Ash.create!(actor: actor)
|
||||
|
||||
assert cycle.status == :unpaid
|
||||
end
|
||||
end
|
||||
|
||||
describe "mark_as_paid" do
|
||||
test "sets status to :paid" do
|
||||
fee_type = create_fee_type(%{interval: :yearly})
|
||||
member = create_member(%{membership_fee_type_id: fee_type.id})
|
||||
cycle = create_cycle(member, fee_type, %{status: :unpaid})
|
||||
test "sets status to :paid", %{actor: actor} do
|
||||
fee_type = create_fee_type(%{interval: :yearly}, actor)
|
||||
member = create_member(%{membership_fee_type_id: fee_type.id}, actor)
|
||||
cycle = create_cycle(member, fee_type, %{status: :unpaid}, actor)
|
||||
|
||||
assert {:ok, updated} = Ash.update(cycle, %{}, action: :mark_as_paid)
|
||||
assert {:ok, updated} = Ash.update(cycle, %{}, actor: actor, action: :mark_as_paid)
|
||||
assert updated.status == :paid
|
||||
end
|
||||
|
||||
test "can set notes when marking as paid" do
|
||||
fee_type = create_fee_type(%{interval: :yearly})
|
||||
member = create_member(%{membership_fee_type_id: fee_type.id})
|
||||
cycle = create_cycle(member, fee_type, %{status: :unpaid})
|
||||
test "can set notes when marking as paid", %{actor: actor} do
|
||||
fee_type = create_fee_type(%{interval: :yearly}, actor)
|
||||
member = create_member(%{membership_fee_type_id: fee_type.id}, actor)
|
||||
cycle = create_cycle(member, fee_type, %{status: :unpaid}, actor)
|
||||
|
||||
assert {:ok, updated} =
|
||||
Ash.update(cycle, %{notes: "Payment received via bank transfer"},
|
||||
actor: actor,
|
||||
action: :mark_as_paid
|
||||
)
|
||||
|
||||
|
|
@ -97,33 +103,34 @@ defmodule Mv.MembershipFees.MembershipFeeCycleTest do
|
|||
assert updated.notes == "Payment received via bank transfer"
|
||||
end
|
||||
|
||||
test "can change from suspended to paid" do
|
||||
fee_type = create_fee_type(%{interval: :yearly})
|
||||
member = create_member(%{membership_fee_type_id: fee_type.id})
|
||||
cycle = create_cycle(member, fee_type, %{status: :suspended})
|
||||
test "can change from suspended to paid", %{actor: actor} do
|
||||
fee_type = create_fee_type(%{interval: :yearly}, actor)
|
||||
member = create_member(%{membership_fee_type_id: fee_type.id}, actor)
|
||||
cycle = create_cycle(member, fee_type, %{status: :suspended}, actor)
|
||||
|
||||
assert {:ok, updated} = Ash.update(cycle, %{}, action: :mark_as_paid)
|
||||
assert {:ok, updated} = Ash.update(cycle, %{}, actor: actor, action: :mark_as_paid)
|
||||
assert updated.status == :paid
|
||||
end
|
||||
end
|
||||
|
||||
describe "mark_as_suspended" do
|
||||
test "sets status to :suspended" do
|
||||
fee_type = create_fee_type(%{interval: :yearly})
|
||||
member = create_member(%{membership_fee_type_id: fee_type.id})
|
||||
cycle = create_cycle(member, fee_type, %{status: :unpaid})
|
||||
test "sets status to :suspended", %{actor: actor} do
|
||||
fee_type = create_fee_type(%{interval: :yearly}, actor)
|
||||
member = create_member(%{membership_fee_type_id: fee_type.id}, actor)
|
||||
cycle = create_cycle(member, fee_type, %{status: :unpaid}, actor)
|
||||
|
||||
assert {:ok, updated} = Ash.update(cycle, %{}, action: :mark_as_suspended)
|
||||
assert {:ok, updated} = Ash.update(cycle, %{}, actor: actor, action: :mark_as_suspended)
|
||||
assert updated.status == :suspended
|
||||
end
|
||||
|
||||
test "can set notes when marking as suspended" do
|
||||
fee_type = create_fee_type(%{interval: :yearly})
|
||||
member = create_member(%{membership_fee_type_id: fee_type.id})
|
||||
cycle = create_cycle(member, fee_type, %{status: :unpaid})
|
||||
test "can set notes when marking as suspended", %{actor: actor} do
|
||||
fee_type = create_fee_type(%{interval: :yearly}, actor)
|
||||
member = create_member(%{membership_fee_type_id: fee_type.id}, actor)
|
||||
cycle = create_cycle(member, fee_type, %{status: :unpaid}, actor)
|
||||
|
||||
assert {:ok, updated} =
|
||||
Ash.update(cycle, %{notes: "Waived due to special circumstances"},
|
||||
actor: actor,
|
||||
action: :mark_as_suspended
|
||||
)
|
||||
|
||||
|
|
@ -131,42 +138,45 @@ defmodule Mv.MembershipFees.MembershipFeeCycleTest do
|
|||
assert updated.notes == "Waived due to special circumstances"
|
||||
end
|
||||
|
||||
test "can change from paid to suspended" do
|
||||
fee_type = create_fee_type(%{interval: :yearly})
|
||||
member = create_member(%{membership_fee_type_id: fee_type.id})
|
||||
cycle = create_cycle(member, fee_type, %{status: :paid})
|
||||
test "can change from paid to suspended", %{actor: actor} do
|
||||
fee_type = create_fee_type(%{interval: :yearly}, actor)
|
||||
member = create_member(%{membership_fee_type_id: fee_type.id}, actor)
|
||||
cycle = create_cycle(member, fee_type, %{status: :paid}, actor)
|
||||
|
||||
assert {:ok, updated} = Ash.update(cycle, %{}, action: :mark_as_suspended)
|
||||
assert {:ok, updated} = Ash.update(cycle, %{}, actor: actor, action: :mark_as_suspended)
|
||||
assert updated.status == :suspended
|
||||
end
|
||||
end
|
||||
|
||||
describe "mark_as_unpaid" do
|
||||
test "sets status to :unpaid" do
|
||||
fee_type = create_fee_type(%{interval: :yearly})
|
||||
member = create_member(%{membership_fee_type_id: fee_type.id})
|
||||
cycle = create_cycle(member, fee_type, %{status: :paid})
|
||||
test "sets status to :unpaid", %{actor: actor} do
|
||||
fee_type = create_fee_type(%{interval: :yearly}, actor)
|
||||
member = create_member(%{membership_fee_type_id: fee_type.id}, actor)
|
||||
cycle = create_cycle(member, fee_type, %{status: :paid}, actor)
|
||||
|
||||
assert {:ok, updated} = Ash.update(cycle, %{}, action: :mark_as_unpaid)
|
||||
assert updated.status == :unpaid
|
||||
end
|
||||
|
||||
test "can set notes when marking as unpaid" do
|
||||
fee_type = create_fee_type(%{interval: :yearly})
|
||||
member = create_member(%{membership_fee_type_id: fee_type.id})
|
||||
cycle = create_cycle(member, fee_type, %{status: :paid})
|
||||
test "can set notes when marking as unpaid", %{actor: actor} do
|
||||
fee_type = create_fee_type(%{interval: :yearly}, actor)
|
||||
member = create_member(%{membership_fee_type_id: fee_type.id}, actor)
|
||||
cycle = create_cycle(member, fee_type, %{status: :paid}, actor)
|
||||
|
||||
assert {:ok, updated} =
|
||||
Ash.update(cycle, %{notes: "Payment was reversed"}, action: :mark_as_unpaid)
|
||||
Ash.update(cycle, %{notes: "Payment was reversed"},
|
||||
actor: actor,
|
||||
action: :mark_as_unpaid
|
||||
)
|
||||
|
||||
assert updated.status == :unpaid
|
||||
assert updated.notes == "Payment was reversed"
|
||||
end
|
||||
|
||||
test "can change from suspended to unpaid" do
|
||||
fee_type = create_fee_type(%{interval: :yearly})
|
||||
member = create_member(%{membership_fee_type_id: fee_type.id})
|
||||
cycle = create_cycle(member, fee_type, %{status: :suspended})
|
||||
test "can change from suspended to unpaid", %{actor: actor} do
|
||||
fee_type = create_fee_type(%{interval: :yearly}, actor)
|
||||
member = create_member(%{membership_fee_type_id: fee_type.id}, actor)
|
||||
cycle = create_cycle(member, fee_type, %{status: :suspended}, actor)
|
||||
|
||||
assert {:ok, updated} = Ash.update(cycle, %{}, action: :mark_as_unpaid)
|
||||
assert updated.status == :unpaid
|
||||
|
|
@ -174,12 +184,12 @@ defmodule Mv.MembershipFees.MembershipFeeCycleTest do
|
|||
end
|
||||
|
||||
describe "status transitions" do
|
||||
test "all status transitions are allowed" do
|
||||
fee_type = create_fee_type(%{interval: :yearly})
|
||||
member = create_member(%{membership_fee_type_id: fee_type.id})
|
||||
test "all status transitions are allowed", %{actor: actor} do
|
||||
fee_type = create_fee_type(%{interval: :yearly}, actor)
|
||||
member = create_member(%{membership_fee_type_id: fee_type.id}, actor)
|
||||
|
||||
# unpaid -> paid
|
||||
cycle1 = create_cycle(member, fee_type, %{status: :unpaid})
|
||||
cycle1 = create_cycle(member, fee_type, %{status: :unpaid}, actor)
|
||||
assert {:ok, c1} = Ash.update(cycle1, %{}, action: :mark_as_paid)
|
||||
assert c1.status == :paid
|
||||
|
||||
|
|
|
|||
|
|
@ -10,8 +10,13 @@ defmodule Mv.MembershipFees.MembershipFeeTypeIntegrationTest do
|
|||
|
||||
require Ash.Query
|
||||
|
||||
setup do
|
||||
system_actor = Mv.Helpers.SystemActor.get_system_actor()
|
||||
%{actor: system_actor}
|
||||
end
|
||||
|
||||
# Helper to create a membership fee type
|
||||
defp create_fee_type(attrs) do
|
||||
defp create_fee_type(attrs, actor) do
|
||||
default_attrs = %{
|
||||
name: "Test Fee Type #{System.unique_integer([:positive])}",
|
||||
amount: Decimal.new("50.00"),
|
||||
|
|
@ -22,11 +27,11 @@ defmodule Mv.MembershipFees.MembershipFeeTypeIntegrationTest do
|
|||
|
||||
MembershipFeeType
|
||||
|> Ash.Changeset.for_create(:create, attrs)
|
||||
|> Ash.create!()
|
||||
|> Ash.create!(actor: actor)
|
||||
end
|
||||
|
||||
describe "admin can create membership fee type" do
|
||||
test "creates type with all fields" do
|
||||
test "creates type with all fields", %{actor: actor} do
|
||||
attrs = %{
|
||||
name: "Standard Membership",
|
||||
amount: Decimal.new("120.00"),
|
||||
|
|
@ -34,7 +39,8 @@ defmodule Mv.MembershipFees.MembershipFeeTypeIntegrationTest do
|
|||
description: "Standard yearly membership fee"
|
||||
}
|
||||
|
||||
assert {:ok, %MembershipFeeType{} = fee_type} = Ash.create(MembershipFeeType, attrs)
|
||||
assert {:ok, %MembershipFeeType{} = fee_type} =
|
||||
Ash.create(MembershipFeeType, attrs, actor: actor)
|
||||
|
||||
assert fee_type.name == "Standard Membership"
|
||||
assert Decimal.equal?(fee_type.amount, Decimal.new("120.00"))
|
||||
|
|
@ -44,88 +50,106 @@ defmodule Mv.MembershipFees.MembershipFeeTypeIntegrationTest do
|
|||
end
|
||||
|
||||
describe "admin can update membership fee type" do
|
||||
setup do
|
||||
setup %{actor: actor} do
|
||||
{:ok, fee_type} =
|
||||
Ash.create(MembershipFeeType, %{
|
||||
name: "Original Name",
|
||||
amount: Decimal.new("100.00"),
|
||||
interval: :yearly,
|
||||
description: "Original description"
|
||||
})
|
||||
Ash.create(
|
||||
MembershipFeeType,
|
||||
%{
|
||||
name: "Original Name",
|
||||
amount: Decimal.new("100.00"),
|
||||
interval: :yearly,
|
||||
description: "Original description"
|
||||
},
|
||||
actor: actor
|
||||
)
|
||||
|
||||
%{fee_type: fee_type}
|
||||
end
|
||||
|
||||
test "can update name", %{fee_type: fee_type} do
|
||||
assert {:ok, updated} = Ash.update(fee_type, %{name: "Updated Name"})
|
||||
test "can update name", %{actor: actor, fee_type: fee_type} do
|
||||
assert {:ok, updated} = Ash.update(fee_type, %{name: "Updated Name"}, actor: actor)
|
||||
assert updated.name == "Updated Name"
|
||||
end
|
||||
|
||||
test "can update amount", %{fee_type: fee_type} do
|
||||
assert {:ok, updated} = Ash.update(fee_type, %{amount: Decimal.new("150.00")})
|
||||
test "can update amount", %{actor: actor, fee_type: fee_type} do
|
||||
assert {:ok, updated} = Ash.update(fee_type, %{amount: Decimal.new("150.00")}, actor: actor)
|
||||
assert Decimal.equal?(updated.amount, Decimal.new("150.00"))
|
||||
end
|
||||
|
||||
test "can update description", %{fee_type: fee_type} do
|
||||
assert {:ok, updated} = Ash.update(fee_type, %{description: "Updated description"})
|
||||
test "can update description", %{actor: actor, fee_type: fee_type} do
|
||||
assert {:ok, updated} =
|
||||
Ash.update(fee_type, %{description: "Updated description"}, actor: actor)
|
||||
|
||||
assert updated.description == "Updated description"
|
||||
end
|
||||
|
||||
test "cannot update interval", %{fee_type: fee_type} do
|
||||
test "cannot update interval", %{actor: actor, fee_type: fee_type} do
|
||||
# Currently, interval is not in the accept list, so it's rejected as "NoSuchInput"
|
||||
# After implementing validation, it should return a validation error
|
||||
assert {:error, error} = Ash.update(fee_type, %{interval: :monthly})
|
||||
assert {:error, error} = Ash.update(fee_type, %{interval: :monthly}, actor: actor)
|
||||
# For now, check that it's an error (either NoSuchInput or validation error)
|
||||
assert %Ash.Error.Invalid{} = error
|
||||
end
|
||||
end
|
||||
|
||||
describe "admin cannot delete membership fee type when in use" do
|
||||
test "cannot delete when members are assigned" do
|
||||
fee_type = create_fee_type(%{interval: :yearly})
|
||||
test "cannot delete when members are assigned", %{actor: actor} do
|
||||
fee_type = create_fee_type(%{interval: :yearly}, actor)
|
||||
|
||||
# Create a member with this fee type
|
||||
{:ok, _member} =
|
||||
Ash.create(Member, %{
|
||||
first_name: "Test",
|
||||
last_name: "Member",
|
||||
email: "test.member.#{System.unique_integer([:positive])}@example.com",
|
||||
membership_fee_type_id: fee_type.id
|
||||
})
|
||||
Ash.create(
|
||||
Member,
|
||||
%{
|
||||
first_name: "Test",
|
||||
last_name: "Member",
|
||||
email: "test.member.#{System.unique_integer([:positive])}@example.com",
|
||||
membership_fee_type_id: fee_type.id
|
||||
},
|
||||
actor: actor
|
||||
)
|
||||
|
||||
assert {:error, error} = Ash.destroy(fee_type)
|
||||
assert {:error, error} = Ash.destroy(fee_type, actor: actor)
|
||||
error_message = extract_error_message(error)
|
||||
assert error_message =~ "member(s) are assigned"
|
||||
end
|
||||
|
||||
test "cannot delete when cycles exist" do
|
||||
fee_type = create_fee_type(%{interval: :yearly})
|
||||
test "cannot delete when cycles exist", %{actor: actor} do
|
||||
fee_type = create_fee_type(%{interval: :yearly}, actor)
|
||||
|
||||
# Create a member with this fee type
|
||||
{:ok, member} =
|
||||
Ash.create(Member, %{
|
||||
first_name: "Test",
|
||||
last_name: "Member",
|
||||
email: "test.member.#{System.unique_integer([:positive])}@example.com",
|
||||
membership_fee_type_id: fee_type.id
|
||||
})
|
||||
Ash.create(
|
||||
Member,
|
||||
%{
|
||||
first_name: "Test",
|
||||
last_name: "Member",
|
||||
email: "test.member.#{System.unique_integer([:positive])}@example.com",
|
||||
membership_fee_type_id: fee_type.id
|
||||
},
|
||||
actor: actor
|
||||
)
|
||||
|
||||
# Create a cycle for this fee type
|
||||
{:ok, _cycle} =
|
||||
Ash.create(MembershipFeeCycle, %{
|
||||
cycle_start: ~D[2025-01-01],
|
||||
amount: Decimal.new("100.00"),
|
||||
member_id: member.id,
|
||||
membership_fee_type_id: fee_type.id
|
||||
})
|
||||
Ash.create(
|
||||
MembershipFeeCycle,
|
||||
%{
|
||||
cycle_start: ~D[2025-01-01],
|
||||
amount: Decimal.new("100.00"),
|
||||
member_id: member.id,
|
||||
membership_fee_type_id: fee_type.id
|
||||
},
|
||||
actor: actor
|
||||
)
|
||||
|
||||
assert {:error, error} = Ash.destroy(fee_type)
|
||||
assert {:error, error} = Ash.destroy(fee_type, actor: actor)
|
||||
error_message = extract_error_message(error)
|
||||
assert error_message =~ "cycle(s) reference"
|
||||
end
|
||||
|
||||
test "cannot delete when used as default in settings" do
|
||||
fee_type = create_fee_type(%{interval: :yearly})
|
||||
test "cannot delete when used as default in settings", %{actor: actor} do
|
||||
fee_type = create_fee_type(%{interval: :yearly}, actor)
|
||||
|
||||
# Set as default in settings
|
||||
{:ok, settings} = Mv.Membership.get_settings()
|
||||
|
|
@ -134,19 +158,19 @@ defmodule Mv.MembershipFees.MembershipFeeTypeIntegrationTest do
|
|||
|> Ash.Changeset.for_update(:update_membership_fee_settings, %{
|
||||
default_membership_fee_type_id: fee_type.id
|
||||
})
|
||||
|> Ash.update!()
|
||||
|> Ash.update!(actor: actor)
|
||||
|
||||
# Try to delete
|
||||
assert {:error, error} = Ash.destroy(fee_type)
|
||||
assert {:error, error} = Ash.destroy(fee_type, actor: actor)
|
||||
error_message = extract_error_message(error)
|
||||
assert error_message =~ "used as default in settings"
|
||||
end
|
||||
end
|
||||
|
||||
describe "settings integration" do
|
||||
test "default_membership_fee_type_id is used during member creation" do
|
||||
test "default_membership_fee_type_id is used during member creation", %{actor: actor} do
|
||||
# Create a fee type
|
||||
fee_type = create_fee_type(%{interval: :yearly})
|
||||
fee_type = create_fee_type(%{interval: :yearly}, actor)
|
||||
|
||||
# Set it as default in settings
|
||||
{:ok, settings} = Mv.Membership.get_settings()
|
||||
|
|
@ -155,29 +179,33 @@ defmodule Mv.MembershipFees.MembershipFeeTypeIntegrationTest do
|
|||
|> Ash.Changeset.for_update(:update_membership_fee_settings, %{
|
||||
default_membership_fee_type_id: fee_type.id
|
||||
})
|
||||
|> Ash.update!()
|
||||
|> Ash.update!(actor: actor)
|
||||
|
||||
# Create a member without explicitly setting membership_fee_type_id
|
||||
# The Member resource automatically assigns the default_membership_fee_type_id
|
||||
# during creation via SetDefaultMembershipFeeType change.
|
||||
{:ok, member} =
|
||||
Ash.create(Member, %{
|
||||
first_name: "Test",
|
||||
last_name: "Member",
|
||||
email: "test.member.#{System.unique_integer([:positive])}@example.com"
|
||||
})
|
||||
Ash.create(
|
||||
Member,
|
||||
%{
|
||||
first_name: "Test",
|
||||
last_name: "Member",
|
||||
email: "test.member.#{System.unique_integer([:positive])}@example.com"
|
||||
},
|
||||
actor: actor
|
||||
)
|
||||
|
||||
# Verify that the default membership fee type was automatically assigned
|
||||
assert member.membership_fee_type_id == fee_type.id
|
||||
end
|
||||
|
||||
test "include_joining_cycle is used during cycle generation" do
|
||||
test "include_joining_cycle is used during cycle generation", %{actor: actor} do
|
||||
# This test verifies that the include_joining_cycle setting affects
|
||||
# cycle generation. The actual cycle generation logic is tested in
|
||||
# CycleGeneratorTest, but this integration test ensures the setting
|
||||
# is properly used.
|
||||
|
||||
fee_type = create_fee_type(%{interval: :yearly})
|
||||
fee_type = create_fee_type(%{interval: :yearly}, actor)
|
||||
|
||||
# Set include_joining_cycle to false
|
||||
{:ok, settings} = Mv.Membership.get_settings()
|
||||
|
|
@ -186,17 +214,21 @@ defmodule Mv.MembershipFees.MembershipFeeTypeIntegrationTest do
|
|||
|> Ash.Changeset.for_update(:update_membership_fee_settings, %{
|
||||
include_joining_cycle: false
|
||||
})
|
||||
|> Ash.update!()
|
||||
|> Ash.update!(actor: actor)
|
||||
|
||||
# Create a member with join_date in the middle of a year
|
||||
{:ok, member} =
|
||||
Ash.create(Member, %{
|
||||
first_name: "Test",
|
||||
last_name: "Member",
|
||||
email: "test.member.#{System.unique_integer([:positive])}@example.com",
|
||||
join_date: ~D[2023-03-15],
|
||||
membership_fee_type_id: fee_type.id
|
||||
})
|
||||
Ash.create(
|
||||
Member,
|
||||
%{
|
||||
first_name: "Test",
|
||||
last_name: "Member",
|
||||
email: "test.member.#{System.unique_integer([:positive])}@example.com",
|
||||
join_date: ~D[2023-03-15],
|
||||
membership_fee_type_id: fee_type.id
|
||||
},
|
||||
actor: actor
|
||||
)
|
||||
|
||||
# Verify that membership_fee_start_date was calculated correctly
|
||||
# (should be 2024-01-01, not 2023-01-01, because include_joining_cycle = false)
|
||||
|
|
|
|||
|
|
@ -6,8 +6,13 @@ defmodule Mv.MembershipFees.MembershipFeeTypeTest do
|
|||
|
||||
alias Mv.MembershipFees.MembershipFeeType
|
||||
|
||||
setup do
|
||||
system_actor = Mv.Helpers.SystemActor.get_system_actor()
|
||||
%{actor: system_actor}
|
||||
end
|
||||
|
||||
describe "create MembershipFeeType" do
|
||||
test "can create membership fee type with valid attributes" do
|
||||
test "can create membership fee type with valid attributes", %{actor: actor} do
|
||||
attrs = %{
|
||||
name: "Standard Membership",
|
||||
amount: Decimal.new("120.00"),
|
||||
|
|
@ -16,7 +21,7 @@ defmodule Mv.MembershipFees.MembershipFeeTypeTest do
|
|||
}
|
||||
|
||||
assert {:ok, %MembershipFeeType{} = fee_type} =
|
||||
Ash.create(MembershipFeeType, attrs)
|
||||
Ash.create(MembershipFeeType, attrs, actor: actor)
|
||||
|
||||
assert fee_type.name == "Standard Membership"
|
||||
assert Decimal.equal?(fee_type.amount, Decimal.new("120.00"))
|
||||
|
|
@ -24,212 +29,237 @@ defmodule Mv.MembershipFees.MembershipFeeTypeTest do
|
|||
assert fee_type.description == "Standard yearly membership fee"
|
||||
end
|
||||
|
||||
test "can create membership fee type without description" do
|
||||
test "can create membership fee type without description", %{actor: actor} do
|
||||
attrs = %{
|
||||
name: "Basic",
|
||||
amount: Decimal.new("60.00"),
|
||||
interval: :monthly
|
||||
}
|
||||
|
||||
assert {:ok, %MembershipFeeType{}} = Ash.create(MembershipFeeType, attrs)
|
||||
assert {:ok, %MembershipFeeType{}} = Ash.create(MembershipFeeType, attrs, actor: actor)
|
||||
end
|
||||
|
||||
test "requires name" do
|
||||
test "requires name", %{actor: actor} do
|
||||
attrs = %{
|
||||
amount: Decimal.new("100.00"),
|
||||
interval: :yearly
|
||||
}
|
||||
|
||||
assert {:error, error} = Ash.create(MembershipFeeType, attrs)
|
||||
assert {:error, error} = Ash.create(MembershipFeeType, attrs, actor: actor)
|
||||
assert error_on_field?(error, :name)
|
||||
end
|
||||
|
||||
test "requires amount" do
|
||||
test "requires amount", %{actor: actor} do
|
||||
attrs = %{
|
||||
name: "Test Fee",
|
||||
interval: :yearly
|
||||
}
|
||||
|
||||
assert {:error, error} = Ash.create(MembershipFeeType, attrs)
|
||||
assert {:error, error} = Ash.create(MembershipFeeType, attrs, actor: actor)
|
||||
assert error_on_field?(error, :amount)
|
||||
end
|
||||
|
||||
test "requires interval" do
|
||||
test "requires interval", %{actor: actor} do
|
||||
attrs = %{
|
||||
name: "Test Fee",
|
||||
amount: Decimal.new("100.00")
|
||||
}
|
||||
|
||||
assert {:error, error} = Ash.create(MembershipFeeType, attrs)
|
||||
assert {:error, error} = Ash.create(MembershipFeeType, attrs, actor: actor)
|
||||
assert error_on_field?(error, :interval)
|
||||
end
|
||||
|
||||
test "validates interval enum values - monthly" do
|
||||
test "validates interval enum values - monthly", %{actor: actor} do
|
||||
attrs = %{name: "Monthly", amount: Decimal.new("10.00"), interval: :monthly}
|
||||
assert {:ok, fee_type} = Ash.create(MembershipFeeType, attrs)
|
||||
assert {:ok, fee_type} = Ash.create(MembershipFeeType, attrs, actor: actor)
|
||||
assert fee_type.interval == :monthly
|
||||
end
|
||||
|
||||
test "validates interval enum values - quarterly" do
|
||||
test "validates interval enum values - quarterly", %{actor: actor} do
|
||||
attrs = %{name: "Quarterly", amount: Decimal.new("30.00"), interval: :quarterly}
|
||||
assert {:ok, fee_type} = Ash.create(MembershipFeeType, attrs)
|
||||
assert {:ok, fee_type} = Ash.create(MembershipFeeType, attrs, actor: actor)
|
||||
assert fee_type.interval == :quarterly
|
||||
end
|
||||
|
||||
test "validates interval enum values - half_yearly" do
|
||||
test "validates interval enum values - half_yearly", %{actor: actor} do
|
||||
attrs = %{name: "Half Yearly", amount: Decimal.new("60.00"), interval: :half_yearly}
|
||||
assert {:ok, fee_type} = Ash.create(MembershipFeeType, attrs)
|
||||
assert {:ok, fee_type} = Ash.create(MembershipFeeType, attrs, actor: actor)
|
||||
assert fee_type.interval == :half_yearly
|
||||
end
|
||||
|
||||
test "validates interval enum values - yearly" do
|
||||
test "validates interval enum values - yearly", %{actor: actor} do
|
||||
attrs = %{name: "Yearly", amount: Decimal.new("120.00"), interval: :yearly}
|
||||
assert {:ok, fee_type} = Ash.create(MembershipFeeType, attrs)
|
||||
assert {:ok, fee_type} = Ash.create(MembershipFeeType, attrs, actor: actor)
|
||||
assert fee_type.interval == :yearly
|
||||
end
|
||||
|
||||
test "rejects invalid interval values" do
|
||||
test "rejects invalid interval values", %{actor: actor} do
|
||||
attrs = %{name: "Invalid", amount: Decimal.new("100.00"), interval: :weekly}
|
||||
assert {:error, error} = Ash.create(MembershipFeeType, attrs)
|
||||
assert {:error, error} = Ash.create(MembershipFeeType, attrs, actor: actor)
|
||||
assert error_on_field?(error, :interval)
|
||||
end
|
||||
|
||||
test "name must be unique" do
|
||||
test "name must be unique", %{actor: actor} do
|
||||
attrs = %{name: "Unique Name", amount: Decimal.new("100.00"), interval: :yearly}
|
||||
|
||||
assert {:ok, _} = Ash.create(MembershipFeeType, attrs)
|
||||
assert {:error, error} = Ash.create(MembershipFeeType, attrs)
|
||||
assert {:ok, _} = Ash.create(MembershipFeeType, attrs, actor: actor)
|
||||
assert {:error, error} = Ash.create(MembershipFeeType, attrs, actor: actor)
|
||||
|
||||
# Check for uniqueness error
|
||||
assert error_on_field?(error, :name)
|
||||
end
|
||||
|
||||
test "rejects negative amount" do
|
||||
test "rejects negative amount", %{actor: actor} do
|
||||
attrs = %{name: "Negative Test", amount: Decimal.new("-10.00"), interval: :yearly}
|
||||
assert {:error, error} = Ash.create(MembershipFeeType, attrs)
|
||||
assert {:error, error} = Ash.create(MembershipFeeType, attrs, actor: actor)
|
||||
assert error_on_field?(error, :amount)
|
||||
end
|
||||
|
||||
test "accepts zero amount" do
|
||||
test "accepts zero amount", %{actor: actor} do
|
||||
attrs = %{name: "Zero Amount", amount: Decimal.new("0.00"), interval: :yearly}
|
||||
assert {:ok, fee_type} = Ash.create(MembershipFeeType, attrs)
|
||||
assert {:ok, fee_type} = Ash.create(MembershipFeeType, attrs, actor: actor)
|
||||
assert Decimal.equal?(fee_type.amount, Decimal.new("0.00"))
|
||||
end
|
||||
|
||||
test "amount respects scale of 2 decimal places" do
|
||||
test "amount respects scale of 2 decimal places", %{actor: actor} do
|
||||
attrs = %{name: "Scale Test", amount: Decimal.new("100.50"), interval: :yearly}
|
||||
assert {:ok, fee_type} = Ash.create(MembershipFeeType, attrs)
|
||||
assert {:ok, fee_type} = Ash.create(MembershipFeeType, attrs, actor: actor)
|
||||
assert Decimal.equal?(fee_type.amount, Decimal.new("100.50"))
|
||||
end
|
||||
end
|
||||
|
||||
describe "update MembershipFeeType" do
|
||||
setup do
|
||||
setup %{actor: actor} do
|
||||
{:ok, fee_type} =
|
||||
Ash.create(MembershipFeeType, %{
|
||||
name: "Original Name",
|
||||
amount: Decimal.new("100.00"),
|
||||
interval: :yearly,
|
||||
description: "Original description"
|
||||
})
|
||||
Ash.create(
|
||||
MembershipFeeType,
|
||||
%{
|
||||
name: "Original Name",
|
||||
amount: Decimal.new("100.00"),
|
||||
interval: :yearly,
|
||||
description: "Original description"
|
||||
},
|
||||
actor: actor
|
||||
)
|
||||
|
||||
%{fee_type: fee_type}
|
||||
end
|
||||
|
||||
test "can update name", %{fee_type: fee_type} do
|
||||
assert {:ok, updated} = Ash.update(fee_type, %{name: "Updated Name"})
|
||||
test "can update name", %{actor: actor, fee_type: fee_type} do
|
||||
assert {:ok, updated} = Ash.update(fee_type, %{name: "Updated Name"}, actor: actor)
|
||||
assert updated.name == "Updated Name"
|
||||
end
|
||||
|
||||
test "can update amount", %{fee_type: fee_type} do
|
||||
assert {:ok, updated} = Ash.update(fee_type, %{amount: Decimal.new("150.00")})
|
||||
test "can update amount", %{actor: actor, fee_type: fee_type} do
|
||||
assert {:ok, updated} = Ash.update(fee_type, %{amount: Decimal.new("150.00")}, actor: actor)
|
||||
assert Decimal.equal?(updated.amount, Decimal.new("150.00"))
|
||||
end
|
||||
|
||||
test "can update description", %{fee_type: fee_type} do
|
||||
assert {:ok, updated} = Ash.update(fee_type, %{description: "Updated description"})
|
||||
test "can update description", %{actor: actor, fee_type: fee_type} do
|
||||
assert {:ok, updated} =
|
||||
Ash.update(fee_type, %{description: "Updated description"}, actor: actor)
|
||||
|
||||
assert updated.description == "Updated description"
|
||||
end
|
||||
|
||||
test "can clear description", %{fee_type: fee_type} do
|
||||
assert {:ok, updated} = Ash.update(fee_type, %{description: nil})
|
||||
test "can clear description", %{actor: actor, fee_type: fee_type} do
|
||||
assert {:ok, updated} = Ash.update(fee_type, %{description: nil}, actor: actor)
|
||||
assert updated.description == nil
|
||||
end
|
||||
|
||||
test "interval immutability: update fails when interval is changed", %{fee_type: fee_type} do
|
||||
test "interval immutability: update fails when interval is changed", %{
|
||||
actor: actor,
|
||||
fee_type: fee_type
|
||||
} do
|
||||
# Currently, interval is not in the accept list, so it's rejected as "NoSuchInput"
|
||||
# After implementing validation, it should return a validation error
|
||||
assert {:error, error} = Ash.update(fee_type, %{interval: :monthly})
|
||||
assert {:error, error} = Ash.update(fee_type, %{interval: :monthly}, actor: actor)
|
||||
# For now, check that it's an error (either NoSuchInput or validation error)
|
||||
assert %Ash.Error.Invalid{} = error
|
||||
end
|
||||
end
|
||||
|
||||
describe "delete MembershipFeeType" do
|
||||
setup do
|
||||
setup %{actor: actor} do
|
||||
{:ok, fee_type} =
|
||||
Ash.create(MembershipFeeType, %{
|
||||
name: "Test Fee Type #{System.unique_integer([:positive])}",
|
||||
amount: Decimal.new("100.00"),
|
||||
interval: :yearly
|
||||
})
|
||||
Ash.create(
|
||||
MembershipFeeType,
|
||||
%{
|
||||
name: "Test Fee Type #{System.unique_integer([:positive])}",
|
||||
amount: Decimal.new("100.00"),
|
||||
interval: :yearly
|
||||
},
|
||||
actor: actor
|
||||
)
|
||||
|
||||
%{fee_type: fee_type}
|
||||
end
|
||||
|
||||
test "can delete when not in use", %{fee_type: fee_type} do
|
||||
result = Ash.destroy(fee_type)
|
||||
test "can delete when not in use", %{actor: actor, fee_type: fee_type} do
|
||||
result = Ash.destroy(fee_type, actor: actor)
|
||||
# Ash.destroy returns :ok or {:ok, _} depending on version
|
||||
assert result == :ok or match?({:ok, _}, result)
|
||||
end
|
||||
|
||||
test "cannot delete when members are assigned", %{fee_type: fee_type} do
|
||||
test "cannot delete when members are assigned", %{actor: actor, fee_type: fee_type} do
|
||||
alias Mv.Membership.Member
|
||||
|
||||
# Create a member with this fee type
|
||||
{:ok, _member} =
|
||||
Ash.create(Member, %{
|
||||
first_name: "Test",
|
||||
last_name: "Member",
|
||||
email: "test.member.#{System.unique_integer([:positive])}@example.com",
|
||||
membership_fee_type_id: fee_type.id
|
||||
})
|
||||
Ash.create(
|
||||
Member,
|
||||
%{
|
||||
first_name: "Test",
|
||||
last_name: "Member",
|
||||
email: "test.member.#{System.unique_integer([:positive])}@example.com",
|
||||
membership_fee_type_id: fee_type.id
|
||||
},
|
||||
actor: actor
|
||||
)
|
||||
|
||||
assert {:error, error} = Ash.destroy(fee_type)
|
||||
assert {:error, error} = Ash.destroy(fee_type, actor: actor)
|
||||
# Check for either validation error message or DB constraint error
|
||||
error_message = extract_error_message(error)
|
||||
assert error_message =~ "member" or error_message =~ "referenced"
|
||||
end
|
||||
|
||||
test "cannot delete when cycles exist", %{fee_type: fee_type} do
|
||||
test "cannot delete when cycles exist", %{actor: actor, fee_type: fee_type} do
|
||||
alias Mv.MembershipFees.MembershipFeeCycle
|
||||
alias Mv.Membership.Member
|
||||
|
||||
# Create a member with this fee type
|
||||
{:ok, member} =
|
||||
Ash.create(Member, %{
|
||||
first_name: "Test",
|
||||
last_name: "Member",
|
||||
email: "test.member.#{System.unique_integer([:positive])}@example.com",
|
||||
membership_fee_type_id: fee_type.id
|
||||
})
|
||||
Ash.create(
|
||||
Member,
|
||||
%{
|
||||
first_name: "Test",
|
||||
last_name: "Member",
|
||||
email: "test.member.#{System.unique_integer([:positive])}@example.com",
|
||||
membership_fee_type_id: fee_type.id
|
||||
},
|
||||
actor: actor
|
||||
)
|
||||
|
||||
# Create a cycle for this fee type
|
||||
{:ok, _cycle} =
|
||||
Ash.create(MembershipFeeCycle, %{
|
||||
cycle_start: ~D[2025-01-01],
|
||||
amount: Decimal.new("100.00"),
|
||||
member_id: member.id,
|
||||
membership_fee_type_id: fee_type.id
|
||||
})
|
||||
Ash.create(
|
||||
MembershipFeeCycle,
|
||||
%{
|
||||
cycle_start: ~D[2025-01-01],
|
||||
amount: Decimal.new("100.00"),
|
||||
member_id: member.id,
|
||||
membership_fee_type_id: fee_type.id
|
||||
},
|
||||
actor: actor
|
||||
)
|
||||
|
||||
assert {:error, error} = Ash.destroy(fee_type)
|
||||
assert {:error, error} = Ash.destroy(fee_type, actor: actor)
|
||||
# Check for either validation error message or DB constraint error
|
||||
error_message = extract_error_message(error)
|
||||
assert error_message =~ "cycle" or error_message =~ "referenced"
|
||||
end
|
||||
|
||||
test "cannot delete when used as default in settings", %{fee_type: fee_type} do
|
||||
test "cannot delete when used as default in settings", %{actor: actor, fee_type: fee_type} do
|
||||
# Set as default in settings
|
||||
{:ok, settings} = Mv.Membership.get_settings()
|
||||
|
||||
|
|
@ -237,10 +267,10 @@ defmodule Mv.MembershipFees.MembershipFeeTypeTest do
|
|||
|> Ash.Changeset.for_update(:update_membership_fee_settings, %{
|
||||
default_membership_fee_type_id: fee_type.id
|
||||
})
|
||||
|> Ash.update!()
|
||||
|> Ash.update!(actor: actor)
|
||||
|
||||
# Try to delete
|
||||
assert {:error, error} = Ash.destroy(fee_type)
|
||||
assert {:error, error} = Ash.destroy(fee_type, actor: actor)
|
||||
error_message = extract_error_message(error)
|
||||
assert error_message =~ "used as default in settings"
|
||||
end
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue