test: fix date dependencies in cycle generator tests

- Add create_member_with_cycles helper that uses fixed 'today' date
- Update tests to use explicit 'today:' option instead of Date.utc_today()
- Prevents test failures when current date changes (e.g., in 2026+)
- Tests now explicitly delete and regenerate cycles with fixed dates
- Ensures consistent test behavior regardless of execution date
This commit is contained in:
Moritz 2025-12-12 16:41:44 +01:00
parent 0b986db635
commit f7fc1f4897
3 changed files with 216 additions and 91 deletions

View file

@ -211,13 +211,18 @@ defmodule Mv.MembershipFees.MemberCycleIntegrationTest do
initial_cycles = get_member_cycles(member.id)
initial_count = length(initial_cycles)
# Manually trigger generation again
{:ok, _} = Mv.MembershipFees.CycleGenerator.generate_cycles_for_member(member.id)
# Use a fixed "today" date to avoid date dependency
# Use a date far enough in the future to ensure all cycles are generated
today = ~D[2025-12-31]
# Manually trigger generation again with fixed "today" date
{:ok, _} =
Mv.MembershipFees.CycleGenerator.generate_cycles_for_member(member.id, today: today)
final_cycles = get_member_cycles(member.id)
final_count = length(final_cycles)
# Should have same number of cycles
# Should have same number of cycles (idempotent)
assert final_count == initial_count
end
end

View file

@ -50,6 +50,46 @@ defmodule Mv.MembershipFees.CycleGeneratorEdgeCasesTest do
|> Ash.create!()
end
# Helper to create a member and explicitly generate cycles with a fixed "today" date.
# This avoids date dependency issues in tests.
#
# Note: We first create the member without fee_type_id, then assign it via update,
# which triggers the after_action hook. However, we then explicitly regenerate
# cycles with the fixed "today" date to ensure consistency.
defp create_member_with_cycles(attrs, today) do
# Extract membership_fee_type_id if present
fee_type_id = Map.get(attrs, :membership_fee_type_id)
# Create member WITHOUT fee type first to avoid auto-generation with real today
attrs_without_fee_type = Map.delete(attrs, :membership_fee_type_id)
member =
create_member(attrs_without_fee_type)
# 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!()
else
member
end
# Explicitly regenerate cycles with fixed "today" date to override any auto-generated cycles
# This ensures the test uses the fixed date, not the real current date
if fee_type_id && member.join_date do
# Delete any existing cycles first to ensure clean state
existing_cycles = get_member_cycles(member.id)
Enum.each(existing_cycles, &Ash.destroy!(&1))
# Generate cycles with fixed "today" date
{:ok, _} = CycleGenerator.generate_cycles_for_member(member.id, today: today)
end
member
end
# Helper to get cycles for a member
defp get_member_cycles(member_id) do
MembershipFeeCycle
@ -74,15 +114,23 @@ defmodule Mv.MembershipFees.CycleGeneratorEdgeCasesTest do
today = ~D[2024-06-15]
# Create member - cycles will be auto-generated
# Create member WITHOUT fee type first to avoid auto-generation with real today
member =
create_member(%{
join_date: today,
membership_fee_type_id: fee_type.id,
membership_fee_start_date: ~D[2024-01-01]
})
# Check all cycles (including auto-generated ones)
# Assign fee type
member =
member
|> Ash.Changeset.for_update(:update_member, %{membership_fee_type_id: fee_type.id})
|> Ash.update!()
# Explicitly generate cycles with fixed "today" date
{:ok, _} = CycleGenerator.generate_cycles_for_member(member.id, today: today)
# Check all cycles
cycles = get_member_cycles(member.id)
# Should have the current year's cycle
@ -96,15 +144,23 @@ defmodule Mv.MembershipFees.CycleGeneratorEdgeCasesTest do
today = ~D[2024-06-15]
# Create member - cycles will be auto-generated
# Create member WITHOUT fee type first to avoid auto-generation with real today
member =
create_member(%{
join_date: today,
membership_fee_type_id: fee_type.id,
membership_fee_start_date: ~D[2024-06-01]
})
# Check all cycles (including auto-generated ones)
# Assign fee type
member =
member
|> Ash.Changeset.for_update(:update_member, %{membership_fee_type_id: fee_type.id})
|> Ash.update!()
# Explicitly generate cycles with fixed "today" date
{:ok, _} = CycleGenerator.generate_cycles_for_member(member.id, today: today)
# Check all cycles
cycles = get_member_cycles(member.id)
# Should have June 2024 cycle
@ -117,15 +173,18 @@ defmodule Mv.MembershipFees.CycleGeneratorEdgeCasesTest do
today = ~D[2024-05-15]
# Create member - cycles will be auto-generated
# Create member and generate cycles with fixed "today" date
member =
create_member(%{
join_date: today,
membership_fee_type_id: fee_type.id,
membership_fee_start_date: ~D[2024-04-01]
})
create_member_with_cycles(
%{
join_date: today,
membership_fee_type_id: fee_type.id,
membership_fee_start_date: ~D[2024-04-01]
},
today
)
# Check all cycles (including auto-generated ones)
# Check all cycles
cycles = get_member_cycles(member.id)
# Should have Q2 2024 cycle
@ -141,14 +200,17 @@ defmodule Mv.MembershipFees.CycleGeneratorEdgeCasesTest do
today = ~D[2024-06-15]
yesterday = Date.add(today, -1)
# Create member - cycles will be auto-generated
# Create member and generate cycles with fixed "today" date
member =
create_member(%{
join_date: ~D[2022-03-15],
exit_date: yesterday,
membership_fee_type_id: fee_type.id,
membership_fee_start_date: ~D[2022-01-01]
})
create_member_with_cycles(
%{
join_date: ~D[2022-03-15],
exit_date: yesterday,
membership_fee_type_id: fee_type.id,
membership_fee_start_date: ~D[2022-01-01]
},
today
)
# Check all cycles
cycles = get_member_cycles(member.id)
@ -214,24 +276,29 @@ defmodule Mv.MembershipFees.CycleGeneratorEdgeCasesTest do
setup_settings(true)
fee_type = create_fee_type(%{interval: :yearly})
# Create member WITH fee type - cycles will be auto-generated
today = ~D[2024-06-15]
# Create member and generate cycles with fixed "today" date
member =
create_member(%{
join_date: ~D[2022-03-15],
membership_fee_type_id: fee_type.id,
membership_fee_start_date: ~D[2022-01-01]
})
create_member_with_cycles(
%{
join_date: ~D[2022-03-15],
membership_fee_type_id: fee_type.id,
membership_fee_start_date: ~D[2022-01-01]
},
today
)
# Check all cycles
cycles = get_member_cycles(member.id)
# Should have generated all cycles from 2022 to current year
assert length(cycles) >= 3
# Should have generated all cycles from 2022 to 2024 (3 cycles)
cycle_years = Enum.map(cycles, & &1.cycle_start.year) |> Enum.sort()
assert 2022 in cycle_years
assert 2023 in cycle_years
assert 2024 in cycle_years
# Should NOT have 2025 (today is 2024-06-15)
refute 2025 in cycle_years
end
end
@ -258,12 +325,16 @@ defmodule Mv.MembershipFees.CycleGeneratorEdgeCasesTest do
})
|> Ash.create!()
# Now assign fee type - this will trigger cycle generation
# Now assign fee type
member =
member
|> Ash.Changeset.for_update(:update_member, %{membership_fee_type_id: fee_type.id})
|> Ash.update!()
# Explicitly generate cycles with fixed "today" date
today = ~D[2024-06-15]
{:ok, _} = CycleGenerator.generate_cycles_for_member(member.id, today: today)
# Check all cycles
all_cycles = get_member_cycles(member.id)
all_cycle_years = Enum.map(all_cycles, & &1.cycle_start.year) |> Enum.sort() |> Enum.uniq()
@ -283,19 +354,24 @@ defmodule Mv.MembershipFees.CycleGeneratorEdgeCasesTest do
setup_settings(true)
fee_type = create_fee_type(%{interval: :yearly})
# Create member - cycles will be auto-generated
today = ~D[2024-06-15]
# Create member and generate cycles with fixed "today" date
member =
create_member(%{
join_date: ~D[2023-11-15],
membership_fee_type_id: fee_type.id,
membership_fee_start_date: ~D[2023-01-01]
})
create_member_with_cycles(
%{
join_date: ~D[2023-11-15],
membership_fee_type_id: fee_type.id,
membership_fee_start_date: ~D[2023-01-01]
},
today
)
# Check all cycles
cycles = get_member_cycles(member.id)
cycle_years = Enum.map(cycles, & &1.cycle_start.year) |> Enum.sort()
# Should have 2023 and 2024 (at least, depending on current date)
# Should have 2023 and 2024
assert 2023 in cycle_years
assert 2024 in cycle_years
end
@ -304,13 +380,18 @@ defmodule Mv.MembershipFees.CycleGeneratorEdgeCasesTest do
setup_settings(true)
fee_type = create_fee_type(%{interval: :quarterly})
# Create member - cycles will be auto-generated
today = ~D[2024-12-15]
# Create member and generate cycles with fixed "today" date
member =
create_member(%{
join_date: ~D[2024-10-15],
membership_fee_type_id: fee_type.id,
membership_fee_start_date: ~D[2024-10-01]
})
create_member_with_cycles(
%{
join_date: ~D[2024-10-15],
membership_fee_type_id: fee_type.id,
membership_fee_start_date: ~D[2024-10-01]
},
today
)
# Check all cycles
cycles = get_member_cycles(member.id)
@ -324,13 +405,18 @@ defmodule Mv.MembershipFees.CycleGeneratorEdgeCasesTest do
setup_settings(true)
fee_type = create_fee_type(%{interval: :monthly})
# Create member - cycles will be auto-generated
today = ~D[2024-12-31]
# Create member and generate cycles with fixed "today" date
member =
create_member(%{
join_date: ~D[2024-12-15],
membership_fee_type_id: fee_type.id,
membership_fee_start_date: ~D[2024-12-01]
})
create_member_with_cycles(
%{
join_date: ~D[2024-12-15],
membership_fee_type_id: fee_type.id,
membership_fee_start_date: ~D[2024-12-01]
},
today
)
# Check all cycles
cycles = get_member_cycles(member.id)
@ -346,14 +432,19 @@ defmodule Mv.MembershipFees.CycleGeneratorEdgeCasesTest do
setup_settings(true)
fee_type = create_fee_type(%{interval: :monthly})
today = ~D[2024-03-15]
# 2024 is a leap year
# Create member - cycles will be auto-generated
# Create member and generate cycles with fixed "today" date
member =
create_member(%{
join_date: ~D[2024-02-15],
membership_fee_type_id: fee_type.id,
membership_fee_start_date: ~D[2024-02-01]
})
create_member_with_cycles(
%{
join_date: ~D[2024-02-15],
membership_fee_type_id: fee_type.id,
membership_fee_start_date: ~D[2024-02-01]
},
today
)
# Check all cycles
cycles = get_member_cycles(member.id)
@ -368,14 +459,19 @@ defmodule Mv.MembershipFees.CycleGeneratorEdgeCasesTest do
setup_settings(true)
fee_type = create_fee_type(%{interval: :monthly})
today = ~D[2023-03-15]
# 2023 is NOT a leap year
# Create member - cycles will be auto-generated
# Create member and generate cycles with fixed "today" date
member =
create_member(%{
join_date: ~D[2023-02-15],
membership_fee_type_id: fee_type.id,
membership_fee_start_date: ~D[2023-02-01]
})
create_member_with_cycles(
%{
join_date: ~D[2023-02-15],
membership_fee_type_id: fee_type.id,
membership_fee_start_date: ~D[2023-02-01]
},
today
)
# Check all cycles
cycles = get_member_cycles(member.id)
@ -390,13 +486,18 @@ defmodule Mv.MembershipFees.CycleGeneratorEdgeCasesTest do
setup_settings(true)
fee_type = create_fee_type(%{interval: :yearly})
# Create member - cycles will be auto-generated
today = ~D[2024-12-31]
# Create member and generate cycles with fixed "today" date
member =
create_member(%{
join_date: ~D[2024-02-29],
membership_fee_type_id: fee_type.id,
membership_fee_start_date: ~D[2024-01-01]
})
create_member_with_cycles(
%{
join_date: ~D[2024-02-29],
membership_fee_type_id: fee_type.id,
membership_fee_start_date: ~D[2024-01-01]
},
today
)
# Check all cycles
cycles = get_member_cycles(member.id)
@ -413,14 +514,19 @@ defmodule Mv.MembershipFees.CycleGeneratorEdgeCasesTest do
setup_settings(true)
fee_type = create_fee_type(%{interval: :yearly})
today = ~D[2024-06-15]
# Member joins mid-2023, should get 2023 cycle with include_joining_cycle=true
# Create member - cycles will be auto-generated
# Create member and generate cycles with fixed "today" date
member =
create_member(%{
join_date: ~D[2023-06-15],
membership_fee_type_id: fee_type.id
# membership_fee_start_date will be auto-calculated
})
create_member_with_cycles(
%{
join_date: ~D[2023-06-15],
membership_fee_type_id: fee_type.id
# membership_fee_start_date will be auto-calculated
},
today
)
# Check all cycles
cycles = get_member_cycles(member.id)
@ -434,14 +540,19 @@ defmodule Mv.MembershipFees.CycleGeneratorEdgeCasesTest do
setup_settings(false)
fee_type = create_fee_type(%{interval: :yearly})
today = ~D[2024-06-15]
# Member joins mid-2023, should start from 2024 with include_joining_cycle=false
# Create member - cycles will be auto-generated
# Create member and generate cycles with fixed "today" date
member =
create_member(%{
join_date: ~D[2023-06-15],
membership_fee_type_id: fee_type.id
# membership_fee_start_date will be auto-calculated
})
create_member_with_cycles(
%{
join_date: ~D[2023-06-15],
membership_fee_type_id: fee_type.id
# membership_fee_start_date will be auto-calculated
},
today
)
# Check all cycles
cycles = get_member_cycles(member.id)
@ -501,14 +612,20 @@ defmodule Mv.MembershipFees.CycleGeneratorEdgeCasesTest do
setup_settings(true)
fee_type = create_fee_type(%{interval: :yearly})
today = ~D[2024-12-31]
# Member exits exactly on cycle start (2024-01-01)
# Create member and generate cycles with fixed "today" date
member =
create_member(%{
join_date: ~D[2022-03-15],
exit_date: ~D[2024-01-01],
membership_fee_type_id: fee_type.id,
membership_fee_start_date: ~D[2022-01-01]
})
create_member_with_cycles(
%{
join_date: ~D[2022-03-15],
exit_date: ~D[2024-01-01],
membership_fee_type_id: fee_type.id,
membership_fee_start_date: ~D[2022-01-01]
},
today
)
# Check cycles
cycles = get_member_cycles(member.id)

View file

@ -63,20 +63,23 @@ defmodule Mv.MembershipFees.CycleGeneratorTest do
setup_settings(true)
fee_type = create_fee_type(%{interval: :yearly})
# Create member WITHOUT fee type first, then assign it
# This avoids the auto-generation on create and gives us control
# Create member WITHOUT fee type first to avoid auto-generation
member =
create_member_without_cycles(%{
join_date: ~D[2022-03-15],
membership_fee_start_date: ~D[2022-01-01]
})
# Assign fee type, which will trigger auto-generation in test env
# Assign fee type
member =
member
|> Ash.Changeset.for_update(:update_member, %{membership_fee_type_id: fee_type.id})
|> Ash.update!()
# Explicitly generate cycles with fixed "today" date to avoid date dependency
today = ~D[2024-06-15]
{:ok, _} = CycleGenerator.generate_cycles_for_member(member.id, today: today)
# Verify cycles were generated
all_cycles = get_member_cycles(member.id)
cycle_years = Enum.map(all_cycles, & &1.cycle_start.year) |> Enum.sort() |> Enum.uniq()