From d9ca6b17638b0d6204f740ce70375c131b33807f Mon Sep 17 00:00:00 2001 From: Moritz Date: Fri, 12 Dec 2025 16:41:44 +0100 Subject: [PATCH] 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 --- .../member_cycle_integration_test.exs | 11 +- .../cycle_generator_edge_cases_test.exs | 287 ++++++++++++------ .../membership_fees/cycle_generator_test.exs | 9 +- 3 files changed, 216 insertions(+), 91 deletions(-) diff --git a/test/membership_fees/member_cycle_integration_test.exs b/test/membership_fees/member_cycle_integration_test.exs index 6b3a5da..acd68a6 100644 --- a/test/membership_fees/member_cycle_integration_test.exs +++ b/test/membership_fees/member_cycle_integration_test.exs @@ -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 diff --git a/test/mv/membership_fees/cycle_generator_edge_cases_test.exs b/test/mv/membership_fees/cycle_generator_edge_cases_test.exs index 3d59f36..adca77a 100644 --- a/test/mv/membership_fees/cycle_generator_edge_cases_test.exs +++ b/test/mv/membership_fees/cycle_generator_edge_cases_test.exs @@ -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) diff --git a/test/mv/membership_fees/cycle_generator_test.exs b/test/mv/membership_fees/cycle_generator_test.exs index e3c918c..06dd59e 100644 --- a/test/mv/membership_fees/cycle_generator_test.exs +++ b/test/mv/membership_fees/cycle_generator_test.exs @@ -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()