Tests: use code interface for Member create/update (actor propagation)
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
Moritz 2026-01-29 15:30:14 +01:00
parent 66f1965af4
commit d77096c800
Signed by: moritz
GPG key ID: 1020A035E5DD0824
32 changed files with 733 additions and 818 deletions

View file

@ -239,13 +239,14 @@ defmodule Mv.Membership.CustomFieldDeletionTest do
# Helper functions
defp create_member(actor) do
Member
|> Ash.Changeset.for_create(:create_member, %{
Mv.Membership.create_member(
%{
first_name: "Test",
last_name: "User#{System.unique_integer([:positive])}",
email: "test#{System.unique_integer([:positive])}@example.com"
})
|> Ash.create(actor: actor)
},
actor: actor
)
end
defp create_custom_field(name, value_type, actor) do

View file

@ -17,13 +17,14 @@ defmodule Mv.Membership.CustomFieldValueValidationTest do
# Create a test member
{:ok, member} =
Member
|> Ash.Changeset.for_create(:create_member, %{
Mv.Membership.create_member(
%{
first_name: "Test",
last_name: "User",
email: "test.validation@example.com"
})
|> Ash.create(actor: system_actor)
},
actor: system_actor
)
# Create custom fields for different types
{:ok, string_field} =

View file

@ -38,10 +38,8 @@ defmodule Mv.Membership.MemberCycleCalculationsTest do
}
attrs = Map.merge(default_attrs, attrs)
Member
|> Ash.Changeset.for_create(:create_member, attrs)
|> Ash.create!(actor: actor)
{:ok, member} = Mv.Membership.create_member(attrs, actor: actor)
member
end
# Helper to create a cycle

View file

@ -14,31 +14,22 @@ defmodule Mv.Membership.MemberSearchWithCustomFieldsTest do
# Create test members
{:ok, member1} =
Member
|> Ash.Changeset.for_create(:create_member, %{
first_name: "Alice",
last_name: "Anderson",
email: "alice@example.com"
})
|> Ash.create(actor: system_actor)
Mv.Membership.create_member(
%{first_name: "Alice", last_name: "Anderson", email: "alice@example.com"},
actor: system_actor
)
{:ok, member2} =
Member
|> Ash.Changeset.for_create(:create_member, %{
first_name: "Bob",
last_name: "Brown",
email: "bob@example.com"
})
|> Ash.create(actor: system_actor)
Mv.Membership.create_member(
%{first_name: "Bob", last_name: "Brown", email: "bob@example.com"},
actor: system_actor
)
{:ok, member3} =
Member
|> Ash.Changeset.for_create(:create_member, %{
first_name: "Charlie",
last_name: "Clark",
email: "charlie@example.com"
})
|> Ash.create(actor: system_actor)
Mv.Membership.create_member(
%{first_name: "Charlie", last_name: "Clark", email: "charlie@example.com"},
actor: system_actor
)
# Create custom fields for different types
{:ok, string_field} =
@ -112,9 +103,7 @@ defmodule Mv.Membership.MemberSearchWithCustomFieldsTest do
# Force search_vector update by reloading member
{:ok, _updated_member} =
member1
|> Ash.Changeset.for_update(:update_member, %{})
|> Ash.update(actor: system_actor)
Mv.Membership.update_member(member1, %{}, actor: system_actor)
# Search for the custom field value
results =
@ -143,9 +132,7 @@ defmodule Mv.Membership.MemberSearchWithCustomFieldsTest do
# Force search_vector update
{:ok, _updated_member} =
member1
|> Ash.Changeset.for_update(:update_member, %{})
|> Ash.update(actor: system_actor)
Mv.Membership.update_member(member1, %{}, actor: system_actor)
# Search for the custom field value
results =
@ -174,9 +161,7 @@ defmodule Mv.Membership.MemberSearchWithCustomFieldsTest do
# Force search_vector update
{:ok, _updated_member} =
member1
|> Ash.Changeset.for_update(:update_member, %{})
|> Ash.update(actor: system_actor)
Mv.Membership.update_member(member1, %{}, actor: system_actor)
# Search for partial custom field value (should work via FTS or custom field filter)
results =
@ -225,9 +210,7 @@ defmodule Mv.Membership.MemberSearchWithCustomFieldsTest do
# Force search_vector update
{:ok, _updated_member} =
member1
|> Ash.Changeset.for_update(:update_member, %{})
|> Ash.update(actor: system_actor)
Mv.Membership.update_member(member1, %{}, actor: system_actor)
# Search for the custom field value (date is stored as text in search_vector)
results =
@ -256,9 +239,7 @@ defmodule Mv.Membership.MemberSearchWithCustomFieldsTest do
# Force search_vector update
{:ok, _updated_member} =
member1
|> Ash.Changeset.for_update(:update_member, %{})
|> Ash.update(actor: system_actor)
Mv.Membership.update_member(member1, %{}, actor: system_actor)
# Search for the custom field value (boolean is stored as "true" or "false" text)
results =
@ -287,9 +268,7 @@ defmodule Mv.Membership.MemberSearchWithCustomFieldsTest do
# Force search_vector update
{:ok, _updated_member} =
member1
|> Ash.Changeset.for_update(:update_member, %{})
|> Ash.update(actor: system_actor)
Mv.Membership.update_member(member1, %{}, actor: system_actor)
# Update custom field value
{:ok, _updated_cfv} =
@ -334,9 +313,7 @@ defmodule Mv.Membership.MemberSearchWithCustomFieldsTest do
# Force search_vector update
{:ok, _updated_member} =
member1
|> Ash.Changeset.for_update(:update_member, %{})
|> Ash.update(actor: system_actor)
Mv.Membership.update_member(member1, %{}, actor: system_actor)
# Verify it's searchable
results =
@ -401,9 +378,7 @@ defmodule Mv.Membership.MemberSearchWithCustomFieldsTest do
# Update member (should trigger search_vector update including custom fields)
{:ok, _updated_member} =
member1
|> Ash.Changeset.for_update(:update_member, %{notes: "Updated notes"})
|> Ash.update(actor: system_actor)
Mv.Membership.update_member(member1, %{notes: "Updated notes"}, actor: system_actor)
# Search should find the custom field value
results =
@ -452,9 +427,7 @@ defmodule Mv.Membership.MemberSearchWithCustomFieldsTest do
# Force search_vector update
{:ok, _updated_member} =
member1
|> Ash.Changeset.for_update(:update_member, %{})
|> Ash.update(actor: system_actor)
Mv.Membership.update_member(member1, %{}, actor: system_actor)
# All values should be searchable
results1 =
@ -496,9 +469,7 @@ defmodule Mv.Membership.MemberSearchWithCustomFieldsTest do
# Force search_vector update
{:ok, _updated_member} =
member1
|> Ash.Changeset.for_update(:update_member, %{})
|> Ash.update(actor: system_actor)
Mv.Membership.update_member(member1, %{}, actor: system_actor)
# Search for full value (should work via search_vector)
results_full =
@ -541,9 +512,7 @@ defmodule Mv.Membership.MemberSearchWithCustomFieldsTest do
# Force search_vector update
{:ok, _updated_member} =
member1
|> Ash.Changeset.for_update(:update_member, %{})
|> Ash.update(actor: system_actor)
Mv.Membership.update_member(member1, %{}, actor: system_actor)
# Search for full phone number (should work via search_vector)
results_full =

View file

@ -41,10 +41,8 @@ defmodule Mv.Membership.MemberTypeChangeIntegrationTest do
}
attrs = Map.merge(default_attrs, attrs)
Member
|> Ash.Changeset.for_create(:create_member, attrs)
|> Ash.create!(actor: actor)
{:ok, member} = Mv.Membership.create_member(attrs, actor: actor)
member
end
# Helper to create a cycle
@ -76,10 +74,14 @@ defmodule Mv.Membership.MemberTypeChangeIntegrationTest do
# Manually assign fee type (this will trigger cycle generation)
member =
member
|> Ash.Changeset.for_update(:update_member, %{
membership_fee_type_id: yearly_type1.id
})
|> Ash.update!(actor: actor)
|> then(fn m ->
{:ok, updated} =
Mv.Membership.update_member(m, %{membership_fee_type_id: yearly_type1.id},
actor: actor
)
updated
end)
# Cycle generation runs synchronously in the same transaction
# No need to wait for async completion
@ -140,11 +142,9 @@ defmodule Mv.Membership.MemberTypeChangeIntegrationTest do
# Change membership fee type (same interval, different amount)
assert {:ok, _updated_member} =
member
|> Ash.Changeset.for_update(:update_member, %{
membership_fee_type_id: yearly_type2.id
})
|> Ash.update(actor: actor)
Mv.Membership.update_member(member, %{membership_fee_type_id: yearly_type2.id},
actor: actor
)
# Cycle regeneration runs synchronously in the same transaction
# No need to wait for async completion
@ -194,10 +194,14 @@ defmodule Mv.Membership.MemberTypeChangeIntegrationTest do
# Manually assign fee type (this will trigger cycle generation)
member =
member
|> Ash.Changeset.for_update(:update_member, %{
membership_fee_type_id: yearly_type1.id
})
|> Ash.update!(actor: actor)
|> then(fn m ->
{:ok, updated} =
Mv.Membership.update_member(m, %{membership_fee_type_id: yearly_type1.id},
actor: actor
)
updated
end)
# Cycle generation runs synchronously in the same transaction
# No need to wait for async completion
@ -215,11 +219,9 @@ defmodule Mv.Membership.MemberTypeChangeIntegrationTest do
# Change membership fee type
assert {:ok, _updated_member} =
member
|> Ash.Changeset.for_update(:update_member, %{
membership_fee_type_id: yearly_type2.id
})
|> Ash.update(actor: actor)
Mv.Membership.update_member(member, %{membership_fee_type_id: yearly_type2.id},
actor: actor
)
# Cycle regeneration runs synchronously in the same transaction
# No need to wait for async completion
@ -242,10 +244,14 @@ defmodule Mv.Membership.MemberTypeChangeIntegrationTest do
# Manually assign fee type (this will trigger cycle generation)
member =
member
|> Ash.Changeset.for_update(:update_member, %{
membership_fee_type_id: yearly_type1.id
})
|> Ash.update!(actor: actor)
|> then(fn m ->
{:ok, updated} =
Mv.Membership.update_member(m, %{membership_fee_type_id: yearly_type1.id},
actor: actor
)
updated
end)
# Cycle generation runs synchronously in the same transaction
# No need to wait for async completion
@ -263,11 +269,9 @@ defmodule Mv.Membership.MemberTypeChangeIntegrationTest do
# Change membership fee type
assert {:ok, _updated_member} =
member
|> Ash.Changeset.for_update(:update_member, %{
membership_fee_type_id: yearly_type2.id
})
|> Ash.update(actor: actor)
Mv.Membership.update_member(member, %{membership_fee_type_id: yearly_type2.id},
actor: actor
)
# Cycle regeneration runs synchronously in the same transaction
# No need to wait for async completion
@ -290,10 +294,14 @@ defmodule Mv.Membership.MemberTypeChangeIntegrationTest do
# Manually assign fee type (this will trigger cycle generation)
member =
member
|> Ash.Changeset.for_update(:update_member, %{
membership_fee_type_id: yearly_type1.id
})
|> Ash.update!(actor: actor)
|> then(fn m ->
{:ok, updated} =
Mv.Membership.update_member(m, %{membership_fee_type_id: yearly_type1.id},
actor: actor
)
updated
end)
# Cycle generation runs synchronously in the same transaction
# No need to wait for async completion
@ -357,11 +365,9 @@ defmodule Mv.Membership.MemberTypeChangeIntegrationTest do
# Change membership fee type
assert {:ok, _updated_member} =
member
|> Ash.Changeset.for_update(:update_member, %{
membership_fee_type_id: yearly_type2.id
})
|> Ash.update(actor: actor)
Mv.Membership.update_member(member, %{membership_fee_type_id: yearly_type2.id},
actor: actor
)
# Cycle regeneration runs synchronously in the same transaction
# No need to wait for async completion
@ -406,10 +412,14 @@ defmodule Mv.Membership.MemberTypeChangeIntegrationTest do
# Manually assign fee type (this will trigger cycle generation)
member =
member
|> Ash.Changeset.for_update(:update_member, %{
membership_fee_type_id: yearly_type1.id
})
|> Ash.update!(actor: actor)
|> then(fn m ->
{:ok, updated} =
Mv.Membership.update_member(m, %{membership_fee_type_id: yearly_type1.id},
actor: actor
)
updated
end)
# Cycle generation runs synchronously in the same transaction
# No need to wait for async completion
@ -463,11 +473,9 @@ defmodule Mv.Membership.MemberTypeChangeIntegrationTest do
# Change membership fee type
assert {:ok, updated_member} =
member
|> Ash.Changeset.for_update(:update_member, %{
membership_fee_type_id: yearly_type2.id
})
|> Ash.update(actor: actor)
Mv.Membership.update_member(member, %{membership_fee_type_id: yearly_type2.id},
actor: actor
)
# Cycle regeneration runs synchronously in the same transaction
# No need to wait for async completion

View file

@ -146,16 +146,17 @@ defmodule Mv.MembershipFees.Changes.SetMembershipFeeStartDateTest do
|> Ash.create!(actor: actor)
# Create member with join_date and fee type but no explicit start date
member =
Mv.Membership.Member
|> Ash.Changeset.for_create(:create_member, %{
{:ok, member} =
Mv.Membership.create_member(
%{
first_name: "Test",
last_name: "User",
email: "test#{System.unique_integer([:positive])}@example.com",
join_date: ~D[2024-03-15],
membership_fee_type_id: fee_type.id
})
|> Ash.create!(actor: actor)
},
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]
@ -177,17 +178,18 @@ defmodule Mv.MembershipFees.Changes.SetMembershipFeeStartDateTest do
# Create member with explicit start date
manual_start_date = ~D[2024-07-01]
member =
Mv.Membership.Member
|> Ash.Changeset.for_create(:create_member, %{
{:ok, member} =
Mv.Membership.create_member(
%{
first_name: "Test",
last_name: "User",
email: "test#{System.unique_integer([:positive])}@example.com",
join_date: ~D[2024-03-15],
membership_fee_type_id: fee_type.id,
membership_fee_start_date: manual_start_date
})
|> Ash.create!(actor: actor)
},
actor: actor
)
# Should keep the manually set date
assert member.membership_fee_start_date == manual_start_date
@ -207,16 +209,17 @@ defmodule Mv.MembershipFees.Changes.SetMembershipFeeStartDateTest do
|> Ash.create!(actor: actor)
# Create member
member =
Mv.Membership.Member
|> Ash.Changeset.for_create(:create_member, %{
{:ok, member} =
Mv.Membership.create_member(
%{
first_name: "Test",
last_name: "User",
email: "test#{System.unique_integer([:positive])}@example.com",
join_date: ~D[2024-03-15],
membership_fee_type_id: fee_type.id
})
|> Ash.create!(actor: actor)
},
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]
@ -236,16 +239,17 @@ defmodule Mv.MembershipFees.Changes.SetMembershipFeeStartDateTest do
|> Ash.create!(actor: actor)
# Create member without join_date
member =
Mv.Membership.Member
|> Ash.Changeset.for_create(:create_member, %{
{:ok, member} =
Mv.Membership.create_member(
%{
first_name: "Test",
last_name: "User",
email: "test#{System.unique_integer([:positive])}@example.com",
membership_fee_type_id: fee_type.id
# No join_date
})
|> Ash.create!(actor: actor)
},
actor: actor
)
# Should not have auto-calculated start date
assert is_nil(member.membership_fee_start_date)
@ -255,16 +259,17 @@ defmodule Mv.MembershipFees.Changes.SetMembershipFeeStartDateTest do
setup_settings(true, actor)
# Create member without fee type
member =
Mv.Membership.Member
|> Ash.Changeset.for_create(:create_member, %{
{:ok, member} =
Mv.Membership.create_member(
%{
first_name: "Test",
last_name: "User",
email: "test#{System.unique_integer([:positive])}@example.com",
join_date: ~D[2024-03-15]
# No membership_fee_type_id
})
|> Ash.create!(actor: actor)
},
actor: actor
)
# Should not have auto-calculated start date
assert is_nil(member.membership_fee_start_date)

View file

@ -4,7 +4,6 @@ defmodule Mv.MembershipFees.Changes.ValidateSameIntervalTest do
"""
use Mv.DataCase, async: true
alias Mv.Membership.Member
alias Mv.MembershipFees.MembershipFeeType
alias Mv.MembershipFees.Changes.ValidateSameInterval
@ -37,10 +36,8 @@ defmodule Mv.MembershipFees.Changes.ValidateSameIntervalTest do
}
attrs = Map.merge(default_attrs, attrs)
Member
|> Ash.Changeset.for_create(:create_member, attrs)
|> Ash.create!(actor: actor)
{:ok, member} = Mv.Membership.create_member(attrs, actor: actor)
member
end
describe "validate_interval_match/1" do
@ -52,9 +49,9 @@ defmodule Mv.MembershipFees.Changes.ValidateSameIntervalTest do
changeset =
member
|> Ash.Changeset.for_update(:update_member, %{
membership_fee_type_id: yearly_type2.id
})
|> Ash.Changeset.for_update(:update_member, %{membership_fee_type_id: yearly_type2.id},
actor: actor
)
|> ValidateSameInterval.change(%{}, %{})
assert changeset.valid?
@ -68,9 +65,9 @@ defmodule Mv.MembershipFees.Changes.ValidateSameIntervalTest do
changeset =
member
|> Ash.Changeset.for_update(:update_member, %{
membership_fee_type_id: monthly_type.id
})
|> Ash.Changeset.for_update(:update_member, %{membership_fee_type_id: monthly_type.id},
actor: actor
)
|> ValidateSameInterval.change(%{}, %{})
refute changeset.valid?
@ -90,9 +87,9 @@ defmodule Mv.MembershipFees.Changes.ValidateSameIntervalTest do
changeset =
member
|> Ash.Changeset.for_update(:update_member, %{
membership_fee_type_id: yearly_type.id
})
|> Ash.Changeset.for_update(:update_member, %{membership_fee_type_id: yearly_type.id},
actor: actor
)
|> ValidateSameInterval.change(%{}, %{})
assert changeset.valid?
@ -104,9 +101,7 @@ defmodule Mv.MembershipFees.Changes.ValidateSameIntervalTest do
changeset =
member
|> Ash.Changeset.for_update(:update_member, %{
membership_fee_type_id: nil
})
|> Ash.Changeset.for_update(:update_member, %{membership_fee_type_id: nil}, actor: actor)
|> ValidateSameInterval.change(%{}, %{})
refute changeset.valid?
@ -124,9 +119,7 @@ defmodule Mv.MembershipFees.Changes.ValidateSameIntervalTest do
changeset =
member
|> Ash.Changeset.for_update(:update_member, %{
first_name: "New Name"
})
|> Ash.Changeset.for_update(:update_member, %{first_name: "New Name"}, actor: actor)
|> ValidateSameInterval.change(%{}, %{})
assert changeset.valid?
@ -140,9 +133,9 @@ defmodule Mv.MembershipFees.Changes.ValidateSameIntervalTest do
changeset =
member
|> Ash.Changeset.for_update(:update_member, %{
membership_fee_type_id: quarterly_type.id
})
|> Ash.Changeset.for_update(:update_member, %{membership_fee_type_id: quarterly_type.id},
actor: actor
)
|> ValidateSameInterval.change(%{}, %{})
error = Enum.find(changeset.errors, &(&1.field == :membership_fee_type_id))
@ -179,9 +172,9 @@ defmodule Mv.MembershipFees.Changes.ValidateSameIntervalTest do
changeset =
member
|> Ash.Changeset.for_update(:update_member, %{
membership_fee_type_id: type2.id
})
|> Ash.Changeset.for_update(:update_member, %{membership_fee_type_id: type2.id},
actor: actor
)
|> ValidateSameInterval.change(%{}, %{})
refute changeset.valid?,
@ -199,11 +192,9 @@ defmodule Mv.MembershipFees.Changes.ValidateSameIntervalTest do
# Try to update member with different interval type
assert {:error, %Ash.Error.Invalid{} = error} =
member
|> Ash.Changeset.for_update(:update_member, %{
membership_fee_type_id: monthly_type.id
})
|> Ash.update(actor: actor)
Mv.Membership.update_member(member, %{membership_fee_type_id: monthly_type.id},
actor: actor
)
# Check that error is about interval mismatch
error_message = extract_error_message(error)
@ -220,11 +211,9 @@ defmodule Mv.MembershipFees.Changes.ValidateSameIntervalTest do
# Update member with same-interval type
assert {:ok, updated_member} =
member
|> Ash.Changeset.for_update(:update_member, %{
membership_fee_type_id: yearly_type2.id
})
|> Ash.update(actor: actor)
Mv.Membership.update_member(member, %{membership_fee_type_id: yearly_type2.id},
actor: actor
)
assert updated_member.membership_fee_type_id == yearly_type2.id
end

View file

@ -52,16 +52,17 @@ defmodule Mv.MembershipFees.MemberCycleIntegrationTest do
setup_settings(true, actor)
fee_type = create_fee_type(%{interval: :yearly}, actor)
member =
Member
|> Ash.Changeset.for_create(:create_member, %{
{:ok, member} =
Mv.Membership.create_member(
%{
first_name: "Test",
last_name: "User",
email: "test#{System.unique_integer([:positive])}@example.com",
join_date: ~D[2023-03-15],
membership_fee_type_id: fee_type.id
})
|> Ash.create!(actor: actor)
},
actor: actor
)
cycles = get_member_cycles(member.id, actor)
@ -80,16 +81,16 @@ defmodule Mv.MembershipFees.MemberCycleIntegrationTest do
test "does not create cycles when member has no fee type", %{actor: actor} do
setup_settings(true, actor)
member =
Member
|> Ash.Changeset.for_create(:create_member, %{
{:ok, member} =
Mv.Membership.create_member(
%{
first_name: "Test",
last_name: "User",
email: "test#{System.unique_integer([:positive])}@example.com",
join_date: ~D[2023-03-15]
# No membership_fee_type_id
})
|> Ash.create!(actor: actor)
},
actor: actor
)
cycles = get_member_cycles(member.id, actor)
@ -100,16 +101,16 @@ defmodule Mv.MembershipFees.MemberCycleIntegrationTest do
setup_settings(true, actor)
fee_type = create_fee_type(%{interval: :yearly}, actor)
member =
Member
|> Ash.Changeset.for_create(:create_member, %{
{:ok, member} =
Mv.Membership.create_member(
%{
first_name: "Test",
last_name: "User",
email: "test#{System.unique_integer([:positive])}@example.com",
membership_fee_type_id: fee_type.id
# No join_date
})
|> Ash.create!(actor: actor)
},
actor: actor
)
cycles = get_member_cycles(member.id, actor)
@ -123,23 +124,23 @@ defmodule Mv.MembershipFees.MemberCycleIntegrationTest do
fee_type = create_fee_type(%{interval: :yearly}, actor)
# Create member without fee type
member =
Member
|> Ash.Changeset.for_create(:create_member, %{
{:ok, member} =
Mv.Membership.create_member(
%{
first_name: "Test",
last_name: "User",
email: "test#{System.unique_integer([:positive])}@example.com",
join_date: ~D[2023-03-15]
})
|> Ash.create!(actor: actor)
},
actor: actor
)
# Verify no cycles yet
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!(actor: actor)
{:ok, member} =
Mv.Membership.update_member(member, %{membership_fee_type_id: fee_type.id}, actor: actor)
cycles = get_member_cycles(member.id, actor)
@ -157,15 +158,19 @@ defmodule Mv.MembershipFees.MemberCycleIntegrationTest do
tasks =
Enum.map(1..5, fn i ->
Task.async(fn ->
Member
|> Ash.Changeset.for_create(:create_member, %{
{:ok, member} =
Mv.Membership.create_member(
%{
first_name: "Test#{i}",
last_name: "User#{i}",
email: "test#{System.unique_integer([:positive])}@example.com",
join_date: ~D[2023-03-15],
membership_fee_type_id: fee_type.id
})
|> Ash.create!(actor: actor)
},
actor: actor
)
member
end)
end)
@ -184,16 +189,17 @@ defmodule Mv.MembershipFees.MemberCycleIntegrationTest do
setup_settings(true, actor)
fee_type = create_fee_type(%{interval: :yearly}, actor)
member =
Member
|> Ash.Changeset.for_create(:create_member, %{
{:ok, member} =
Mv.Membership.create_member(
%{
first_name: "Test",
last_name: "User",
email: "test#{System.unique_integer([:positive])}@example.com",
join_date: ~D[2023-03-15],
membership_fee_type_id: fee_type.id
})
|> Ash.create!(actor: actor)
},
actor: actor
)
initial_cycles = get_member_cycles(member.id, actor)
initial_count = length(initial_cycles)

View file

@ -37,10 +37,8 @@ defmodule Mv.MembershipFees.MembershipFeeCycleTest do
}
attrs = Map.merge(default_attrs, attrs)
Member
|> Ash.Changeset.for_create(:create_member, attrs)
|> Ash.create!(actor: actor)
{:ok, member} = Mv.Membership.create_member(attrs, actor: actor)
member
end
# Helper to create a cycle

View file

@ -9,7 +9,7 @@ defmodule Mv.Membership.CustomFieldValuePoliciesTest do
# async: false because we need database commits to be visible across queries
use Mv.DataCase, async: false
alias Mv.Membership.{CustomField, CustomFieldValue, Member}
alias Mv.Membership.{CustomField, CustomFieldValue}
alias Mv.Accounts
alias Mv.Authorization
@ -62,13 +62,14 @@ defmodule Mv.Membership.CustomFieldValuePoliciesTest do
defp create_linked_member_for_user(user, actor) do
{:ok, member} =
Member
|> Ash.Changeset.for_create(:create_member, %{
Mv.Membership.create_member(
%{
first_name: "Linked",
last_name: "Member",
email: "linked#{System.unique_integer([:positive])}@example.com"
})
|> Ash.create(actor: actor, return_notifications?: false)
},
actor: actor
)
user
|> Ash.Changeset.for_update(:update, %{})
@ -80,13 +81,14 @@ defmodule Mv.Membership.CustomFieldValuePoliciesTest do
defp create_unlinked_member(actor) do
{:ok, member} =
Member
|> Ash.Changeset.for_create(:create_member, %{
Mv.Membership.create_member(
%{
first_name: "Unlinked",
last_name: "Member",
email: "unlinked#{System.unique_integer([:positive])}@example.com"
})
|> Ash.create(actor: actor)
},
actor: actor
)
member
end

View file

@ -78,13 +78,14 @@ defmodule Mv.Membership.MemberPoliciesTest do
# NOTE: We need to ensure the member is actually persisted to the database
# before we try to link it. Ash may delay writes, so we explicitly return the struct.
{:ok, member} =
Membership.Member
|> Ash.Changeset.for_create(:create_member, %{
Membership.create_member(
%{
first_name: "Linked",
last_name: "Member",
email: "linked#{System.unique_integer([:positive])}@example.com"
})
|> Ash.create(actor: admin, return_notifications?: false)
},
actor: admin
)
# Link member to user (User.member_id = member.id)
# We use force_change_attribute because the member already exists and we just
@ -108,13 +109,14 @@ defmodule Mv.Membership.MemberPoliciesTest do
admin = create_admin_user(actor)
{:ok, member} =
Membership.Member
|> Ash.Changeset.for_create(:create_member, %{
Membership.create_member(
%{
first_name: "Unlinked",
last_name: "Member",
email: "unlinked#{System.unique_integer([:positive])}@example.com"
})
|> Ash.create(actor: admin)
},
actor: admin
)
member
end
@ -145,9 +147,7 @@ defmodule Mv.Membership.MemberPoliciesTest do
# Update is allowed via HasPermission check with :linked scope (not via special case)
# The special case policy only applies to :read actions
{:ok, updated_member} =
linked_member
|> Ash.Changeset.for_update(:update_member, %{first_name: "Updated"})
|> Ash.update(actor: user)
Membership.update_member(linked_member, %{first_name: "Updated"}, actor: user)
assert updated_member.first_name == "Updated"
end
@ -168,11 +168,8 @@ defmodule Mv.Membership.MemberPoliciesTest do
user: user,
unlinked_member: unlinked_member
} do
assert_raise Ash.Error.Forbidden, fn ->
unlinked_member
|> Ash.Changeset.for_update(:update_member, %{first_name: "Updated"})
|> Ash.update!(actor: user)
end
assert {:error, %Ash.Error.Forbidden{}} =
Membership.update_member(unlinked_member, %{first_name: "Updated"}, actor: user)
end
test "list members returns only linked member", %{
@ -187,15 +184,15 @@ defmodule Mv.Membership.MemberPoliciesTest do
end
test "cannot create member (returns forbidden)", %{user: user} do
assert_raise Ash.Error.Forbidden, fn ->
Membership.Member
|> Ash.Changeset.for_create(:create_member, %{
assert {:error, %Ash.Error.Forbidden{}} =
Membership.create_member(
%{
first_name: "New",
last_name: "Member",
email: "new#{System.unique_integer([:positive])}@example.com"
})
|> Ash.create!(actor: user)
end
},
actor: user
)
end
test "cannot destroy member (returns forbidden)", %{
@ -245,26 +242,23 @@ defmodule Mv.Membership.MemberPoliciesTest do
end
test "cannot create member (returns forbidden)", %{user: user} do
assert_raise Ash.Error.Forbidden, fn ->
Membership.Member
|> Ash.Changeset.for_create(:create_member, %{
assert {:error, %Ash.Error.Forbidden{}} =
Membership.create_member(
%{
first_name: "New",
last_name: "Member",
email: "new#{System.unique_integer([:positive])}@example.com"
})
|> Ash.create!(actor: user)
end
},
actor: user
)
end
test "cannot update any member (returns forbidden)", %{
user: user,
linked_member: linked_member
} do
assert_raise Ash.Error.Forbidden, fn ->
linked_member
|> Ash.Changeset.for_update(:update_member, %{first_name: "Updated"})
|> Ash.update!(actor: user)
end
assert {:error, %Ash.Error.Forbidden{}} =
Membership.update_member(linked_member, %{first_name: "Updated"}, actor: user)
end
test "cannot destroy any member (returns forbidden)", %{
@ -305,22 +299,21 @@ defmodule Mv.Membership.MemberPoliciesTest do
test "can create member", %{user: user} do
{:ok, member} =
Membership.Member
|> Ash.Changeset.for_create(:create_member, %{
Membership.create_member(
%{
first_name: "New",
last_name: "Member",
email: "new#{System.unique_integer([:positive])}@example.com"
})
|> Ash.create(actor: user)
},
actor: user
)
assert member.first_name == "New"
end
test "can update any member", %{user: user, unlinked_member: unlinked_member} do
{:ok, updated_member} =
unlinked_member
|> Ash.Changeset.for_update(:update_member, %{first_name: "Updated"})
|> Ash.update(actor: user)
Membership.update_member(unlinked_member, %{first_name: "Updated"}, actor: user)
assert updated_member.first_name == "Updated"
end
@ -363,22 +356,21 @@ defmodule Mv.Membership.MemberPoliciesTest do
test "can create member", %{user: user} do
{:ok, member} =
Membership.Member
|> Ash.Changeset.for_create(:create_member, %{
Membership.create_member(
%{
first_name: "New",
last_name: "Member",
email: "new#{System.unique_integer([:positive])}@example.com"
})
|> Ash.create(actor: user)
},
actor: user
)
assert member.first_name == "New"
end
test "can update any member", %{user: user, unlinked_member: unlinked_member} do
{:ok, updated_member} =
unlinked_member
|> Ash.Changeset.for_update(:update_member, %{first_name: "Updated"})
|> Ash.update(actor: user)
Membership.update_member(unlinked_member, %{first_name: "Updated"}, actor: user)
assert updated_member.first_name == "Updated"
end
@ -456,9 +448,7 @@ defmodule Mv.Membership.MemberPoliciesTest do
# Should succeed via HasPermission check (not special case)
{:ok, updated_member} =
linked_member
|> Ash.Changeset.for_update(:update_member, %{first_name: "Updated"})
|> Ash.update(actor: user)
Membership.update_member(linked_member, %{first_name: "Updated"}, actor: user)
assert updated_member.first_name == "Updated"
end

View file

@ -49,10 +49,8 @@ defmodule Mv.MembershipFees.CycleGeneratorEdgeCasesTest do
}
attrs = Map.merge(default_attrs, attrs)
Member
|> Ash.Changeset.for_create(:create_member, attrs)
|> Ash.create!(actor: actor)
{:ok, member} = Mv.Membership.create_member(attrs, actor: actor)
member
end
# Helper to create a member and explicitly generate cycles with a fixed "today" date.
@ -74,9 +72,12 @@ defmodule Mv.MembershipFees.CycleGeneratorEdgeCasesTest do
# Assign fee type if provided (this will trigger auto-generation with real today)
member =
if fee_type_id do
member
|> Ash.Changeset.for_update(:update_member, %{membership_fee_type_id: fee_type_id})
|> Ash.update!(actor: actor)
{:ok, updated} =
Mv.Membership.update_member(member, %{membership_fee_type_id: fee_type_id},
actor: actor
)
updated
else
member
end
@ -130,10 +131,8 @@ defmodule Mv.MembershipFees.CycleGeneratorEdgeCasesTest do
)
# Assign fee type
member =
member
|> Ash.Changeset.for_update(:update_member, %{membership_fee_type_id: fee_type.id})
|> Ash.update!(actor: actor)
{:ok, member} =
Mv.Membership.update_member(member, %{membership_fee_type_id: fee_type.id}, actor: actor)
# Explicitly generate cycles with fixed "today" date
{:ok, _, _} = CycleGenerator.generate_cycles_for_member(member.id, today: today)
@ -163,10 +162,8 @@ defmodule Mv.MembershipFees.CycleGeneratorEdgeCasesTest do
)
# Assign fee type
member =
member
|> Ash.Changeset.for_update(:update_member, %{membership_fee_type_id: fee_type.id})
|> Ash.update!(actor: actor)
{:ok, member} =
Mv.Membership.update_member(member, %{membership_fee_type_id: fee_type.id}, actor: actor)
# Explicitly generate cycles with fixed "today" date
{:ok, _, _} = CycleGenerator.generate_cycles_for_member(member.id, today: today)
@ -349,10 +346,8 @@ defmodule Mv.MembershipFees.CycleGeneratorEdgeCasesTest do
|> Ash.create!(actor: actor)
# Now assign fee type
member =
member
|> Ash.Changeset.for_update(:update_member, %{membership_fee_type_id: fee_type.id})
|> Ash.update!(actor: actor)
{:ok, member} =
Mv.Membership.update_member(member, %{membership_fee_type_id: fee_type.id}, actor: actor)
# Explicitly generate cycles with fixed "today" date
today = ~D[2024-06-15]
@ -616,13 +611,15 @@ defmodule Mv.MembershipFees.CycleGeneratorEdgeCasesTest do
)
# Now assign fee type (simulating a retroactive assignment)
member =
member
|> Ash.Changeset.for_update(:update_member, %{
{:ok, member} =
Mv.Membership.update_member(
member,
%{
membership_fee_type_id: fee_type.id,
membership_fee_start_date: ~D[2021-01-01]
})
|> Ash.update!(actor: actor)
},
actor: actor
)
# Run batch generation with a "today" date after the member left
today = ~D[2024-06-15]

View file

@ -40,10 +40,8 @@ defmodule Mv.MembershipFees.CycleGeneratorTest do
}
attrs = Map.merge(default_attrs, attrs)
Member
|> Ash.Changeset.for_create(:create_member, attrs)
|> Ash.create!(actor: actor)
{:ok, member} = Mv.Membership.create_member(attrs, actor: actor)
member
end
# Helper to set up settings with specific include_joining_cycle value
@ -81,8 +79,12 @@ defmodule Mv.MembershipFees.CycleGeneratorTest do
# Assign fee type
member =
member
|> Ash.Changeset.for_update(:update_member, %{membership_fee_type_id: fee_type.id})
|> Ash.update!(actor: actor)
|> then(fn m ->
{:ok, updated} =
Mv.Membership.update_member(m, %{membership_fee_type_id: fee_type.id}, actor: actor)
updated
end)
# Explicitly generate cycles with fixed "today" date to avoid date dependency
today = ~D[2024-06-15]
@ -128,8 +130,12 @@ defmodule Mv.MembershipFees.CycleGeneratorTest do
# Now assign fee type to member
member =
member
|> Ash.Changeset.for_update(:update_member, %{membership_fee_type_id: fee_type.id})
|> Ash.update!(actor: actor)
|> then(fn m ->
{:ok, updated} =
Mv.Membership.update_member(m, %{membership_fee_type_id: fee_type.id}, actor: actor)
updated
end)
# Generate cycles with specific "today" date
today = ~D[2024-06-15]
@ -237,8 +243,12 @@ defmodule Mv.MembershipFees.CycleGeneratorTest do
# start from the last existing cycle (2023) and go forward
member =
member
|> Ash.Changeset.for_update(:update_member, %{membership_fee_type_id: fee_type.id})
|> Ash.update!(actor: actor)
|> then(fn m ->
{:ok, updated} =
Mv.Membership.update_member(m, %{membership_fee_type_id: fee_type.id}, actor: actor)
updated
end)
# Verify gap was NOT filled and new cycles were generated from last existing
all_cycles = get_member_cycles(member.id, actor)

View file

@ -80,21 +80,20 @@ defmodule MvWeb.Helpers.MembershipFeeHelpersTest do
|> Ash.create!(actor: actor)
# Create member without fee type first to avoid auto-generation
member =
Mv.Membership.Member
|> Ash.Changeset.for_create(:create_member, %{
{: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]
})
|> Ash.create!(actor: actor)
},
actor: actor
)
# 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!(actor: actor)
{:ok, member} =
Mv.Membership.update_member(member, %{membership_fee_type_id: fee_type.id}, actor: actor)
# Delete any auto-generated cycles first
cycles =
@ -151,20 +150,19 @@ defmodule MvWeb.Helpers.MembershipFeeHelpersTest do
|> Ash.create!(actor: actor)
# Create member without fee type first
member =
Mv.Membership.Member
|> Ash.Changeset.for_create(:create_member, %{
{:ok, member} =
Mv.Membership.create_member(
%{
first_name: "Test",
last_name: "Member",
email: "test#{System.unique_integer([:positive])}@example.com"
})
|> Ash.create!(actor: actor)
},
actor: actor
)
# Assign fee type
member =
member
|> Ash.Changeset.for_update(:update_member, %{membership_fee_type_id: fee_type.id})
|> Ash.update!(actor: actor)
{:ok, member} =
Mv.Membership.update_member(member, %{membership_fee_type_id: fee_type.id}, actor: actor)
# Delete any auto-generated cycles
cycles =
@ -197,21 +195,20 @@ defmodule MvWeb.Helpers.MembershipFeeHelpersTest do
|> Ash.create!(actor: actor)
# Create member without fee type first
member =
Mv.Membership.Member
|> Ash.Changeset.for_create(:create_member, %{
{: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]
})
|> Ash.create!(actor: actor)
},
actor: actor
)
# Assign fee type
member =
member
|> Ash.Changeset.for_update(:update_member, %{membership_fee_type_id: fee_type.id})
|> Ash.update!(actor: actor)
{:ok, member} =
Mv.Membership.update_member(member, %{membership_fee_type_id: fee_type.id}, actor: actor)
# Delete any auto-generated cycles
cycles =

View file

@ -41,7 +41,8 @@ defmodule MvWeb.CustomFieldLive.DeletionTest do
user_with_role = Ash.load!(user, :role, domain: Mv.Accounts, actor: system_actor)
conn = log_in_user(build_conn(), user_with_role)
# Use English locale so "Delete" link text matches in assertions
conn = Plug.Test.init_test_session(conn, Map.put(conn.private.plug_session, "locale", "en"))
session = conn.private[:plug_session] || %{}
conn = Plug.Test.init_test_session(conn, Map.put(session, "locale", "en"))
%{conn: conn, user: user_with_role}
end
@ -271,17 +272,18 @@ defmodule MvWeb.CustomFieldLive.DeletionTest do
end
end
# Helper functions
# Helper functions (use code interface so actor is in validation context)
defp create_member do
system_actor = Mv.Helpers.SystemActor.get_system_actor()
Member
|> Ash.Changeset.for_create(:create_member, %{
Mv.Membership.create_member(
%{
first_name: "Test",
last_name: "User#{System.unique_integer([:positive])}",
email: "test#{System.unique_integer([:positive])}@example.com"
})
|> Ash.create(actor: system_actor)
},
actor: system_actor
)
end
defp create_custom_field(name, value_type) do

View file

@ -7,7 +7,6 @@ defmodule MvWeb.MembershipFeeTypeLive.FormTest do
import Phoenix.LiveViewTest
alias Mv.MembershipFees.MembershipFeeType
alias Mv.Membership.Member
require Ash.Query
@ -55,10 +54,8 @@ defmodule MvWeb.MembershipFeeTypeLive.FormTest do
}
attrs = Map.merge(default_attrs, attrs)
Member
|> Ash.Changeset.for_create(:create_member, attrs)
|> Ash.create!(actor: system_actor)
{:ok, member} = Mv.Membership.create_member(attrs, actor: system_actor)
member
end
describe "create form" do

View file

@ -7,7 +7,6 @@ defmodule MvWeb.MembershipFeeTypeLive.IndexTest do
import Phoenix.LiveViewTest
alias Mv.MembershipFees.MembershipFeeType
alias Mv.Membership.Member
require Ash.Query
@ -31,7 +30,7 @@ defmodule MvWeb.MembershipFeeTypeLive.IndexTest do
end
# Helper to create a member
# Uses admin actor from global setup to ensure authorization
# Uses admin actor from global setup to ensure authorization; falls back to system_actor when nil
defp create_member(attrs, actor) do
default_attrs = %{
first_name: "Test",
@ -40,12 +39,9 @@ defmodule MvWeb.MembershipFeeTypeLive.IndexTest do
}
attrs = Map.merge(default_attrs, attrs)
opts = if actor, do: [actor: actor], else: []
Member
|> Ash.Changeset.for_create(:create_member, attrs)
|> Ash.create!(opts)
effective_actor = actor || Mv.Helpers.SystemActor.get_system_actor()
{:ok, member} = Mv.Membership.create_member(attrs, actor: effective_actor)
member
end
describe "list display" do

View file

@ -68,13 +68,10 @@ defmodule MvWeb.UserLive.ShowTest do
# Create member
{:ok, member} =
Member
|> Ash.Changeset.for_create(:create_member, %{
first_name: "Alice",
last_name: "Smith",
email: "alice@example.com"
})
|> Ash.create(actor: system_actor)
Mv.Membership.create_member(
%{first_name: "Alice", last_name: "Smith", email: "alice@example.com"},
actor: system_actor
)
# Create user and link to member
user = create_test_user(%{email: "user@example.com"})

View file

@ -6,8 +6,6 @@ defmodule MvWeb.MemberLive.FormErrorHandlingTest do
import Phoenix.LiveViewTest
alias Mv.Membership.Member
require Ash.Query
describe "error handling - flash messages" do
@ -16,13 +14,14 @@ defmodule MvWeb.MemberLive.FormErrorHandlingTest do
# Create a member with the same email to trigger uniqueness error
{:ok, _existing_member} =
Member
|> Ash.Changeset.for_create(:create_member, %{
Mv.Membership.create_member(
%{
first_name: "Existing",
last_name: "Member",
email: "duplicate@example.com"
})
|> Ash.create(actor: system_actor)
},
actor: system_actor
)
conn = conn_with_oidc_user(conn)
{:ok, view, _html} = live(conn, "/members/new")
@ -79,23 +78,21 @@ defmodule MvWeb.MemberLive.FormErrorHandlingTest do
# Create a member to edit
{:ok, member} =
Member
|> Ash.Changeset.for_create(:create_member, %{
Mv.Membership.create_member(
%{
first_name: "Original",
last_name: "Member",
email: "original@example.com"
})
|> Ash.create(actor: system_actor)
},
actor: system_actor
)
# Create another member with different email
{:ok, _other_member} =
Member
|> Ash.Changeset.for_create(:create_member, %{
first_name: "Other",
last_name: "Member",
email: "other@example.com"
})
|> Ash.create(actor: system_actor)
Mv.Membership.create_member(
%{first_name: "Other", last_name: "Member", email: "other@example.com"},
actor: system_actor
)
conn = conn_with_oidc_user(conn)
{:ok, view, _html} = live(conn, "/members/#{member.id}/edit")

View file

@ -6,7 +6,6 @@ defmodule MvWeb.MemberLive.FormMembershipFeeTypeTest do
import Phoenix.LiveViewTest
alias Mv.Membership.Member
alias Mv.MembershipFees.MembershipFeeType
require Ash.Query
@ -37,10 +36,8 @@ defmodule MvWeb.MemberLive.FormMembershipFeeTypeTest do
}
attrs = Map.merge(default_attrs, attrs)
Member
|> Ash.Changeset.for_create(:create_member, attrs)
|> Ash.create!(actor: admin_user)
{:ok, member} = Mv.Membership.create_member(attrs, actor: admin_user)
member
end
describe "membership fee type dropdown" do
@ -129,7 +126,7 @@ defmodule MvWeb.MemberLive.FormMembershipFeeTypeTest do
# Verify member was created with fee type - use admin_user to test permissions
member =
Member
Mv.Membership.Member
|> Ash.Query.filter(email == ^form_data["member[email]"])
|> Ash.read_one!(actor: admin_user)
@ -177,14 +174,15 @@ defmodule MvWeb.MemberLive.FormMembershipFeeTypeTest do
# Create member with fee type 1 and custom field value
member =
Member
|> Ash.Changeset.for_create(:create_member, %{
create_member(
%{
first_name: "Test",
last_name: "Member",
email: "test#{System.unique_integer([:positive])}@example.com",
membership_fee_type_id: fee_type1.id
})
|> Ash.create!(actor: admin_user)
},
admin_user
)
# Add custom field value
Mv.Membership.CustomFieldValue
@ -222,14 +220,15 @@ defmodule MvWeb.MemberLive.FormMembershipFeeTypeTest do
# Create member with date custom field value
member =
Member
|> Ash.Changeset.for_create(:create_member, %{
create_member(
%{
first_name: "Test",
last_name: "Member",
email: "test#{System.unique_integer([:positive])}@example.com",
membership_fee_type_id: fee_type.id
})
|> Ash.create!(actor: admin_user)
},
admin_user
)
test_date = ~D[2024-01-15]
@ -269,14 +268,15 @@ defmodule MvWeb.MemberLive.FormMembershipFeeTypeTest do
# Create member with custom field value
member =
Member
|> Ash.Changeset.for_create(:create_member, %{
create_member(
%{
first_name: "Test",
last_name: "Member",
email: "test#{System.unique_integer([:positive])}@example.com",
membership_fee_type_id: fee_type.id
})
|> Ash.create!(actor: admin_user)
},
admin_user
)
# Add custom field value
_cfv =

View file

@ -39,10 +39,8 @@ defmodule MvWeb.MemberLive.Index.MembershipFeeStatusTest do
}
attrs = Map.merge(default_attrs, attrs)
Member
|> Ash.Changeset.for_create(:create_member, attrs)
|> Ash.create!(actor: system_actor)
{:ok, member} = Mv.Membership.create_member(attrs, actor: system_actor)
member
end
# Helper to create a cycle
@ -106,8 +104,14 @@ defmodule MvWeb.MemberLive.Index.MembershipFeeStatusTest do
# Assign fee type
member =
member
|> Ash.Changeset.for_update(:update_member, %{membership_fee_type_id: fee_type.id})
|> Ash.update!(actor: system_actor)
|> then(fn m ->
{:ok, updated} =
Mv.Membership.update_member(m, %{membership_fee_type_id: fee_type.id},
actor: system_actor
)
updated
end)
# Delete any auto-generated cycles
cycles =
@ -151,8 +155,14 @@ defmodule MvWeb.MemberLive.Index.MembershipFeeStatusTest do
# Assign fee type
member =
member
|> Ash.Changeset.for_update(:update_member, %{membership_fee_type_id: fee_type.id})
|> Ash.update!(actor: system_actor)
|> then(fn m ->
{:ok, updated} =
Mv.Membership.update_member(m, %{membership_fee_type_id: fee_type.id},
actor: system_actor
)
updated
end)
# Delete any auto-generated cycles
cycles =
@ -192,8 +202,14 @@ defmodule MvWeb.MemberLive.Index.MembershipFeeStatusTest do
# Assign fee type
member =
member
|> Ash.Changeset.for_update(:update_member, %{membership_fee_type_id: fee_type.id})
|> Ash.update!(actor: system_actor)
|> then(fn m ->
{:ok, updated} =
Mv.Membership.update_member(m, %{membership_fee_type_id: fee_type.id},
actor: system_actor
)
updated
end)
# Delete any auto-generated cycles
cycles =

View file

@ -11,20 +11,17 @@ defmodule MvWeb.MemberLive.IndexCustomFieldsAccessibilityTest do
import Phoenix.LiveViewTest
require Ash.Query
alias Mv.Membership.{CustomField, CustomFieldValue, Member}
alias Mv.Membership.{CustomField, CustomFieldValue}
setup do
system_actor = Mv.Helpers.SystemActor.get_system_actor()
# Create test member
{:ok, member} =
Member
|> Ash.Changeset.for_create(:create_member, %{
first_name: "Alice",
last_name: "Anderson",
email: "alice@example.com"
})
|> Ash.create(actor: system_actor)
Mv.Membership.create_member(
%{first_name: "Alice", last_name: "Anderson", email: "alice@example.com"},
actor: system_actor
)
# Create custom field with show_in_overview: true
{:ok, field} =

View file

@ -14,29 +14,23 @@ defmodule MvWeb.MemberLive.IndexCustomFieldsDisplayTest do
import Phoenix.LiveViewTest
require Ash.Query
alias Mv.Membership.{CustomField, CustomFieldValue, Member}
alias Mv.Membership.{CustomField, CustomFieldValue}
setup do
system_actor = Mv.Helpers.SystemActor.get_system_actor()
# Create test members
{:ok, member1} =
Member
|> Ash.Changeset.for_create(:create_member, %{
first_name: "Alice",
last_name: "Anderson",
email: "alice@example.com"
})
|> Ash.create(actor: system_actor)
Mv.Membership.create_member(
%{first_name: "Alice", last_name: "Anderson", email: "alice@example.com"},
actor: system_actor
)
{:ok, member2} =
Member
|> Ash.Changeset.for_create(:create_member, %{
first_name: "Bob",
last_name: "Brown",
email: "bob@example.com"
})
|> Ash.create(actor: system_actor)
Mv.Membership.create_member(
%{first_name: "Bob", last_name: "Brown", email: "bob@example.com"},
actor: system_actor
)
# Create custom fields
{:ok, field_show_string} =

View file

@ -10,29 +10,23 @@ defmodule MvWeb.MemberLive.IndexCustomFieldsEdgeCasesTest do
import Phoenix.LiveViewTest
require Ash.Query
alias Mv.Membership.{CustomField, Member}
alias Mv.Membership.CustomField
test "displays custom field column even when no members have values", %{conn: conn} do
system_actor = Mv.Helpers.SystemActor.get_system_actor()
# Create test members without custom field values
{:ok, _member1} =
Member
|> Ash.Changeset.for_create(:create_member, %{
first_name: "Alice",
last_name: "Anderson",
email: "alice@example.com"
})
|> Ash.create(actor: system_actor)
Mv.Membership.create_member(
%{first_name: "Alice", last_name: "Anderson", email: "alice@example.com"},
actor: system_actor
)
{:ok, _member2} =
Member
|> Ash.Changeset.for_create(:create_member, %{
first_name: "Bob",
last_name: "Brown",
email: "bob@example.com"
})
|> Ash.create(actor: system_actor)
Mv.Membership.create_member(
%{first_name: "Bob", last_name: "Brown", email: "bob@example.com"},
actor: system_actor
)
# Create custom field with show_in_overview: true but no values
{:ok, field} =
@ -56,13 +50,10 @@ defmodule MvWeb.MemberLive.IndexCustomFieldsEdgeCasesTest do
# Create test member
{:ok, member} =
Member
|> Ash.Changeset.for_create(:create_member, %{
first_name: "Alice",
last_name: "Anderson",
email: "alice@example.com"
})
|> Ash.create(actor: system_actor)
Mv.Membership.create_member(
%{first_name: "Alice", last_name: "Anderson", email: "alice@example.com"},
actor: system_actor
)
# Create custom field
{:ok, field} =
@ -99,13 +90,10 @@ defmodule MvWeb.MemberLive.IndexCustomFieldsEdgeCasesTest do
# Create test member
{:ok, member} =
Member
|> Ash.Changeset.for_create(:create_member, %{
first_name: "Alice",
last_name: "Anderson",
email: "alice@example.com"
})
|> Ash.create(actor: system_actor)
Mv.Membership.create_member(
%{first_name: "Alice", last_name: "Anderson", email: "alice@example.com"},
actor: system_actor
)
# Create multiple custom fields with show_in_overview: true
{:ok, field1} =

View file

@ -13,38 +13,29 @@ defmodule MvWeb.MemberLive.IndexCustomFieldsSortingTest do
import Phoenix.LiveViewTest
require Ash.Query
alias Mv.Membership.{CustomField, CustomFieldValue, Member}
alias Mv.Membership.{CustomField, CustomFieldValue}
setup do
system_actor = Mv.Helpers.SystemActor.get_system_actor()
# Create test members
{:ok, member1} =
Member
|> Ash.Changeset.for_create(:create_member, %{
first_name: "Alice",
last_name: "Anderson",
email: "alice@example.com"
})
|> Ash.create(actor: system_actor)
Mv.Membership.create_member(
%{first_name: "Alice", last_name: "Anderson", email: "alice@example.com"},
actor: system_actor
)
{:ok, member2} =
Member
|> Ash.Changeset.for_create(:create_member, %{
first_name: "Bob",
last_name: "Brown",
email: "bob@example.com"
})
|> Ash.create(actor: system_actor)
Mv.Membership.create_member(
%{first_name: "Bob", last_name: "Brown", email: "bob@example.com"},
actor: system_actor
)
{:ok, member3} =
Member
|> Ash.Changeset.for_create(:create_member, %{
first_name: "Charlie",
last_name: "Clark",
email: "charlie@example.com"
})
|> Ash.create(actor: system_actor)
Mv.Membership.create_member(
%{first_name: "Charlie", last_name: "Clark", email: "charlie@example.com"},
actor: system_actor
)
# Create custom field with show_in_overview: true
{:ok, field_string} =
@ -242,40 +233,28 @@ defmodule MvWeb.MemberLive.IndexCustomFieldsSortingTest do
# Create additional members with NULL and empty string values
{:ok, member_with_value} =
Member
|> Ash.Changeset.for_create(:create_member, %{
first_name: "WithValue",
last_name: "Test",
email: "withvalue@example.com"
})
|> Ash.create(actor: system_actor)
Mv.Membership.create_member(
%{first_name: "WithValue", last_name: "Test", email: "withvalue@example.com"},
actor: system_actor
)
{:ok, member_with_empty} =
Member
|> Ash.Changeset.for_create(:create_member, %{
first_name: "WithEmpty",
last_name: "Test",
email: "withempty@example.com"
})
|> Ash.create(actor: system_actor)
Mv.Membership.create_member(
%{first_name: "WithEmpty", last_name: "Test", email: "withempty@example.com"},
actor: system_actor
)
{:ok, member_with_null} =
Member
|> Ash.Changeset.for_create(:create_member, %{
first_name: "WithNull",
last_name: "Test",
email: "withnull@example.com"
})
|> Ash.create(actor: system_actor)
Mv.Membership.create_member(
%{first_name: "WithNull", last_name: "Test", email: "withnull@example.com"},
actor: system_actor
)
{:ok, member_with_another_value} =
Member
|> Ash.Changeset.for_create(:create_member, %{
first_name: "AnotherValue",
last_name: "Test",
email: "another@example.com"
})
|> Ash.create(actor: system_actor)
Mv.Membership.create_member(
%{first_name: "AnotherValue", last_name: "Test", email: "another@example.com"},
actor: system_actor
)
# Create custom field
{:ok, field} =
@ -355,40 +334,28 @@ defmodule MvWeb.MemberLive.IndexCustomFieldsSortingTest do
# Create additional members with NULL and empty string values
{:ok, member_with_value} =
Member
|> Ash.Changeset.for_create(:create_member, %{
first_name: "WithValue",
last_name: "Test",
email: "withvalue@example.com"
})
|> Ash.create(actor: system_actor)
Mv.Membership.create_member(
%{first_name: "WithValue", last_name: "Test", email: "withvalue@example.com"},
actor: system_actor
)
{:ok, member_with_empty} =
Member
|> Ash.Changeset.for_create(:create_member, %{
first_name: "WithEmpty",
last_name: "Test",
email: "withempty@example.com"
})
|> Ash.create(actor: system_actor)
Mv.Membership.create_member(
%{first_name: "WithEmpty", last_name: "Test", email: "withempty@example.com"},
actor: system_actor
)
{:ok, member_with_null} =
Member
|> Ash.Changeset.for_create(:create_member, %{
first_name: "WithNull",
last_name: "Test",
email: "withnull@example.com"
})
|> Ash.create(actor: system_actor)
Mv.Membership.create_member(
%{first_name: "WithNull", last_name: "Test", email: "withnull@example.com"},
actor: system_actor
)
{:ok, member_with_another_value} =
Member
|> Ash.Changeset.for_create(:create_member, %{
first_name: "AnotherValue",
last_name: "Test",
email: "another@example.com"
})
|> Ash.create(actor: system_actor)
Mv.Membership.create_member(
%{first_name: "AnotherValue", last_name: "Test", email: "another@example.com"},
actor: system_actor
)
# Create custom field
{:ok, field} =

View file

@ -16,33 +16,35 @@ defmodule MvWeb.MemberLive.IndexFieldVisibilityTest do
import Phoenix.LiveViewTest
require Ash.Query
alias Mv.Membership.{CustomField, CustomFieldValue, Member}
alias Mv.Membership.{CustomField, CustomFieldValue}
setup do
system_actor = Mv.Helpers.SystemActor.get_system_actor()
# Create test members
{:ok, member1} =
Member
|> Ash.Changeset.for_create(:create_member, %{
Mv.Membership.create_member(
%{
first_name: "Alice",
last_name: "Anderson",
email: "alice@example.com",
street: "Main St",
city: "Berlin"
})
|> Ash.create(actor: system_actor)
},
actor: system_actor
)
{:ok, member2} =
Member
|> Ash.Changeset.for_create(:create_member, %{
Mv.Membership.create_member(
%{
first_name: "Bob",
last_name: "Brown",
email: "bob@example.com",
street: "Second St",
city: "Hamburg"
})
|> Ash.create(actor: system_actor)
},
actor: system_actor
)
# Create custom field
{:ok, custom_field} =

View file

@ -3,14 +3,12 @@ defmodule MvWeb.MemberLive.IndexMemberFieldsDisplayTest do
import Phoenix.LiveViewTest
require Ash.Query
alias Mv.Membership.Member
setup do
system_actor = Mv.Helpers.SystemActor.get_system_actor()
{:ok, member1} =
Member
|> Ash.Changeset.for_create(:create_member, %{
Mv.Membership.create_member(
%{
first_name: "Alice",
last_name: "Anderson",
email: "alice@example.com",
@ -19,17 +17,15 @@ defmodule MvWeb.MemberLive.IndexMemberFieldsDisplayTest do
postal_code: "12345",
city: "Berlin",
join_date: ~D[2020-01-15]
})
|> Ash.create(actor: system_actor)
},
actor: system_actor
)
{:ok, member2} =
Member
|> Ash.Changeset.for_create(:create_member, %{
first_name: "Bob",
last_name: "Brown",
email: "bob@example.com"
})
|> Ash.create(actor: system_actor)
Mv.Membership.create_member(
%{first_name: "Bob", last_name: "Brown", email: "bob@example.com"},
actor: system_actor
)
%{
member1: member1,

View file

@ -6,7 +6,6 @@ defmodule MvWeb.MemberLive.IndexMembershipFeeStatusTest do
import Phoenix.LiveViewTest
alias Mv.Membership.Member
alias Mv.MembershipFees.MembershipFeeType
alias Mv.MembershipFees.MembershipFeeCycle
@ -40,10 +39,8 @@ defmodule MvWeb.MemberLive.IndexMembershipFeeStatusTest do
}
attrs = Map.merge(default_attrs, attrs)
Member
|> Ash.Changeset.for_create(:create_member, attrs)
|> Ash.create!(actor: system_actor)
{:ok, member} = Mv.Membership.create_member(attrs, actor: system_actor)
member
end
# Helper to create a cycle

View file

@ -531,10 +531,8 @@ defmodule MvWeb.MemberLive.IndexTest do
}
attrs = Map.merge(default_attrs, attrs)
Mv.Membership.Member
|> Ash.Changeset.for_create(:create_member, attrs)
|> Ash.create!(actor: actor)
{:ok, member} = Mv.Membership.create_member(attrs, actor: actor)
member
end
test "filter shows only members with paid status in last cycle", %{conn: conn} do
@ -1127,18 +1125,15 @@ defmodule MvWeb.MemberLive.IndexTest do
# Helper to create a member with a boolean custom field value
defp create_member_with_boolean_value(member_attrs, custom_field, value, actor) do
{:ok, member} =
Mv.Membership.Member
|> Ash.Changeset.for_create(
:create_member,
attrs =
%{
first_name: "Test",
last_name: "Member",
email: "test.member.#{System.unique_integer([:positive])}@example.com"
}
|> Map.merge(member_attrs)
)
|> Ash.create(actor: actor)
{:ok, member} = Mv.Membership.create_member(attrs, actor: actor)
{:ok, _cfv} =
Mv.Membership.CustomFieldValue
@ -1182,13 +1177,14 @@ defmodule MvWeb.MemberLive.IndexTest do
boolean_field = create_boolean_custom_field()
{:ok, member} =
Mv.Membership.Member
|> Ash.Changeset.for_create(:create_member, %{
Mv.Membership.create_member(
%{
first_name: "Test",
last_name: "Member",
email: "test.member.#{System.unique_integer([:positive])}@example.com"
})
|> Ash.create(actor: system_actor)
},
actor: system_actor
)
# Create CustomFieldValue with map format (Ash expects _union_type and _union_value)
{:ok, _cfv} =
@ -1215,13 +1211,14 @@ defmodule MvWeb.MemberLive.IndexTest do
boolean_field = create_boolean_custom_field()
{:ok, member} =
Mv.Membership.Member
|> Ash.Changeset.for_create(:create_member, %{
Mv.Membership.create_member(
%{
first_name: "Test",
last_name: "Member",
email: "test.member.#{System.unique_integer([:positive])}@example.com"
})
|> Ash.create(actor: system_actor)
},
actor: system_actor
)
# Member has no custom field value for this field
member = member |> Ash.load!(:custom_field_values, actor: system_actor)
@ -1238,13 +1235,14 @@ defmodule MvWeb.MemberLive.IndexTest do
boolean_field = create_boolean_custom_field()
{:ok, member} =
Mv.Membership.Member
|> Ash.Changeset.for_create(:create_member, %{
Mv.Membership.create_member(
%{
first_name: "Test",
last_name: "Member",
email: "test.member.#{System.unique_integer([:positive])}@example.com"
})
|> Ash.create(actor: system_actor)
},
actor: system_actor
)
# Create CustomFieldValue with nil value (edge case)
{:ok, _cfv} =
@ -1271,13 +1269,14 @@ defmodule MvWeb.MemberLive.IndexTest do
boolean_field = create_boolean_custom_field()
{:ok, member} =
Mv.Membership.Member
|> Ash.Changeset.for_create(:create_member, %{
Mv.Membership.create_member(
%{
first_name: "Test",
last_name: "Member",
email: "test.member.#{System.unique_integer([:positive])}@example.com"
})
|> Ash.create(actor: system_actor)
},
actor: system_actor
)
# Create string custom field value (not boolean)
{:ok, _cfv} =
@ -1320,13 +1319,14 @@ defmodule MvWeb.MemberLive.IndexTest do
)
{:ok, member_without_value} =
Mv.Membership.Member
|> Ash.Changeset.for_create(:create_member, %{
Mv.Membership.create_member(
%{
first_name: "NoValue",
last_name: "Member",
email: "novalue.member.#{System.unique_integer([:positive])}@example.com"
})
|> Ash.create(actor: system_actor)
},
actor: system_actor
)
member_without_value =
member_without_value |> Ash.load!(:custom_field_values, actor: system_actor)
@ -1370,13 +1370,14 @@ defmodule MvWeb.MemberLive.IndexTest do
)
{:ok, member_without_value} =
Mv.Membership.Member
|> Ash.Changeset.for_create(:create_member, %{
Mv.Membership.create_member(
%{
first_name: "NoValue",
last_name: "Member",
email: "novalue.member.#{System.unique_integer([:positive])}@example.com"
})
|> Ash.create(actor: system_actor)
},
actor: system_actor
)
member_without_value =
member_without_value |> Ash.load!(:custom_field_values, actor: system_actor)
@ -1447,13 +1448,14 @@ defmodule MvWeb.MemberLive.IndexTest do
# Member with both fields = true
{:ok, member_both_true} =
Mv.Membership.Member
|> Ash.Changeset.for_create(:create_member, %{
Mv.Membership.create_member(
%{
first_name: "BothTrue",
last_name: "Member",
email: "bothtrue.member.#{System.unique_integer([:positive])}@example.com"
})
|> Ash.create(actor: system_actor)
},
actor: system_actor
)
{:ok, _cfv1} =
Mv.Membership.CustomFieldValue
@ -1477,13 +1479,14 @@ defmodule MvWeb.MemberLive.IndexTest do
# Member with field1 = true, field2 = false
{:ok, member_mixed} =
Mv.Membership.Member
|> Ash.Changeset.for_create(:create_member, %{
Mv.Membership.create_member(
%{
first_name: "Mixed",
last_name: "Member",
email: "mixed.member.#{System.unique_integer([:positive])}@example.com"
})
|> Ash.create(actor: system_actor)
},
actor: system_actor
)
{:ok, _cfv3} =
Mv.Membership.CustomFieldValue
@ -1580,13 +1583,14 @@ defmodule MvWeb.MemberLive.IndexTest do
)
{:ok, _member_without_value} =
Mv.Membership.Member
|> Ash.Changeset.for_create(:create_member, %{
Mv.Membership.create_member(
%{
first_name: "NoValue",
last_name: "Member",
email: "novalue.member.#{System.unique_integer([:positive])}@example.com"
})
|> Ash.create(actor: system_actor)
},
actor: system_actor
)
# Test true filter
{:ok, _view, html_true} =
@ -1615,14 +1619,15 @@ defmodule MvWeb.MemberLive.IndexTest do
# Member with true boolean value and paid status
{:ok, member_paid_true} =
Mv.Membership.Member
|> Ash.Changeset.for_create(:create_member, %{
Mv.Membership.create_member(
%{
first_name: "PaidTrue",
last_name: "Member",
email: "paidtrue.member.#{System.unique_integer([:positive])}@example.com",
membership_fee_type_id: fee_type.id
})
|> Ash.create(actor: system_actor)
},
actor: system_actor
)
{:ok, _cfv} =
Mv.Membership.CustomFieldValue
@ -1642,14 +1647,15 @@ defmodule MvWeb.MemberLive.IndexTest do
# Member with true boolean value but unpaid status
{:ok, member_unpaid_true} =
Mv.Membership.Member
|> Ash.Changeset.for_create(:create_member, %{
Mv.Membership.create_member(
%{
first_name: "UnpaidTrue",
last_name: "Member",
email: "unpaidtrue.member.#{System.unique_integer([:positive])}@example.com",
membership_fee_type_id: fee_type.id
})
|> Ash.create(actor: system_actor)
},
actor: system_actor
)
{:ok, _cfv2} =
Mv.Membership.CustomFieldValue
@ -1730,13 +1736,14 @@ defmodule MvWeb.MemberLive.IndexTest do
)
{:ok, _member_without_value} =
Mv.Membership.Member
|> Ash.Changeset.for_create(:create_member, %{
Mv.Membership.create_member(
%{
first_name: "NoValue",
last_name: "Member",
email: "novalue.member.#{System.unique_integer([:positive])}@example.com"
})
|> Ash.create(actor: system_actor)
},
actor: system_actor
)
# Test that filter works even though field is not visible in overview
{:ok, _view, html_true} =

View file

@ -6,7 +6,6 @@ defmodule MvWeb.MemberLive.MembershipFeeIntegrationTest do
import Phoenix.LiveViewTest
alias Mv.Membership.Member
alias Mv.MembershipFees.MembershipFeeType
alias Mv.MembershipFees.MembershipFeeCycle
@ -40,10 +39,8 @@ defmodule MvWeb.MemberLive.MembershipFeeIntegrationTest do
}
attrs = Map.merge(default_attrs, attrs)
Member
|> Ash.Changeset.for_create(:create_member, attrs)
|> Ash.create!(actor: system_actor)
{:ok, member} = Mv.Membership.create_member(attrs, actor: system_actor)
member
end
describe "end-to-end workflows" do
@ -152,7 +149,7 @@ defmodule MvWeb.MemberLive.MembershipFeeIntegrationTest do
system_actor = Mv.Helpers.SystemActor.get_system_actor()
member =
Member
Mv.Membership.Member
|> Ash.Query.filter(email == ^form_data["member[email]"])
|> Ash.read_one!(actor: system_actor)

View file

@ -6,7 +6,6 @@ defmodule MvWeb.MemberLive.ShowMembershipFeesTest do
import Phoenix.LiveViewTest
alias Mv.Membership.Member
alias Mv.MembershipFees.MembershipFeeType
alias Mv.MembershipFees.MembershipFeeCycle
@ -40,10 +39,8 @@ defmodule MvWeb.MemberLive.ShowMembershipFeesTest do
}
attrs = Map.merge(default_attrs, attrs)
Member
|> Ash.Changeset.for_create(:create_member, attrs)
|> Ash.create!(actor: system_actor)
{:ok, member} = Mv.Membership.create_member(attrs, actor: system_actor)
member
end
# Helper to create a cycle

View file

@ -18,20 +18,17 @@ defmodule MvWeb.MemberLive.ShowTest do
require Ash.Query
use Gettext, backend: MvWeb.Gettext
alias Mv.Membership.{CustomField, CustomFieldValue, Member}
alias Mv.Membership.{CustomField, CustomFieldValue}
setup do
system_actor = Mv.Helpers.SystemActor.get_system_actor()
# Create test member
{:ok, member} =
Member
|> Ash.Changeset.for_create(:create_member, %{
first_name: "Alice",
last_name: "Anderson",
email: "alice@example.com"
})
|> Ash.create(actor: system_actor)
Mv.Membership.create_member(
%{first_name: "Alice", last_name: "Anderson", email: "alice@example.com"},
actor: system_actor
)
%{member: member, actor: system_actor}
end