refactor: improve CalendarCycles API and tests based on code review
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
This commit is contained in:
parent
822d06ed54
commit
b257c9897f
2 changed files with 127 additions and 237 deletions
|
|
@ -2,10 +2,16 @@ defmodule Mv.MembershipFees.CalendarCycles do
|
|||
@moduledoc """
|
||||
Calendar-based cycle calculation functions for membership fees.
|
||||
|
||||
This module provides pure functions for calculating cycle boundaries
|
||||
This module provides functions for calculating cycle boundaries
|
||||
based on interval types (monthly, quarterly, half-yearly, yearly).
|
||||
|
||||
All functions are pure (no side effects) and work with Elixir's `Date` type.
|
||||
The calculation functions (`calculate_cycle_start/3`, `calculate_cycle_end/2`,
|
||||
`next_cycle_start/2`) are pure functions with no side effects.
|
||||
|
||||
The time-dependent functions (`current_cycle?/3`, `last_completed_cycle?/3`)
|
||||
depend on a date parameter for testability. Their 2-argument variants
|
||||
(`current_cycle?/2`, `last_completed_cycle?/2`) use `Date.utc_today()` and
|
||||
are not referentially transparent.
|
||||
|
||||
## Interval Types
|
||||
|
||||
|
|
@ -29,47 +35,61 @@ defmodule Mv.MembershipFees.CalendarCycles do
|
|||
~D[2025-01-01]
|
||||
"""
|
||||
|
||||
@typedoc """
|
||||
Interval type for membership fee cycles.
|
||||
|
||||
- `:monthly` - Monthly cycles (1st to last day of month)
|
||||
- `:quarterly` - Quarterly cycles (1st of quarter to last day of quarter)
|
||||
- `:half_yearly` - Half-yearly cycles (1st of half-year to last day of half-year)
|
||||
- `:yearly` - Yearly cycles (Jan 1st to Dec 31st)
|
||||
"""
|
||||
@type interval :: :monthly | :quarterly | :half_yearly | :yearly
|
||||
|
||||
@doc """
|
||||
Calculates the start date of the cycle that contains the given date.
|
||||
Calculates the start date of the cycle that contains the reference date.
|
||||
|
||||
## Parameters
|
||||
|
||||
- `date` - The date for which to find the cycle start
|
||||
- `date` - The date for which to find the cycle start (used as default if `reference_date` not provided)
|
||||
- `interval` - The interval type (`:monthly`, `:quarterly`, `:half_yearly`, `:yearly`)
|
||||
- `reference_date` - Optional reference date (defaults to `date`)
|
||||
- `reference_date` - The reference date to use for calculation (defaults to `date`)
|
||||
|
||||
## Returns
|
||||
|
||||
The start date of the cycle containing the given date.
|
||||
The start date of the cycle containing the reference date.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> calculate_cycle_start(~D[2024-03-15], :monthly)
|
||||
iex> Mv.MembershipFees.CalendarCycles.calculate_cycle_start(~D[2024-03-15], :monthly)
|
||||
~D[2024-03-01]
|
||||
|
||||
iex> calculate_cycle_start(~D[2024-05-15], :quarterly)
|
||||
iex> Mv.MembershipFees.CalendarCycles.calculate_cycle_start(~D[2024-05-15], :quarterly)
|
||||
~D[2024-04-01]
|
||||
|
||||
iex> calculate_cycle_start(~D[2024-09-15], :half_yearly)
|
||||
iex> Mv.MembershipFees.CalendarCycles.calculate_cycle_start(~D[2024-09-15], :half_yearly)
|
||||
~D[2024-07-01]
|
||||
|
||||
iex> calculate_cycle_start(~D[2024-12-15], :yearly)
|
||||
iex> Mv.MembershipFees.CalendarCycles.calculate_cycle_start(~D[2024-12-15], :yearly)
|
||||
~D[2024-01-01]
|
||||
"""
|
||||
@spec calculate_cycle_start(Date.t(), interval(), Date.t() | nil) :: Date.t()
|
||||
def calculate_cycle_start(date, interval, reference_date \\ nil) do
|
||||
reference = reference_date || date
|
||||
|
||||
iex> Mv.MembershipFees.CalendarCycles.calculate_cycle_start(~D[2024-03-15], :monthly, ~D[2024-05-20])
|
||||
~D[2024-05-01]
|
||||
"""
|
||||
@spec calculate_cycle_start(Date.t(), interval(), Date.t()) :: Date.t()
|
||||
def calculate_cycle_start(_date, interval, reference_date) do
|
||||
case interval do
|
||||
:monthly -> monthly_cycle_start(reference)
|
||||
:quarterly -> quarterly_cycle_start(reference)
|
||||
:half_yearly -> half_yearly_cycle_start(reference)
|
||||
:yearly -> yearly_cycle_start(reference)
|
||||
:monthly -> monthly_cycle_start(reference_date)
|
||||
:quarterly -> quarterly_cycle_start(reference_date)
|
||||
:half_yearly -> half_yearly_cycle_start(reference_date)
|
||||
:yearly -> yearly_cycle_start(reference_date)
|
||||
end
|
||||
end
|
||||
|
||||
@spec calculate_cycle_start(Date.t(), interval()) :: Date.t()
|
||||
def calculate_cycle_start(date, interval) do
|
||||
calculate_cycle_start(date, interval, date)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Calculates the end date of a cycle based on its start date and interval.
|
||||
|
||||
|
|
@ -84,19 +104,19 @@ defmodule Mv.MembershipFees.CalendarCycles do
|
|||
|
||||
## Examples
|
||||
|
||||
iex> calculate_cycle_end(~D[2024-03-01], :monthly)
|
||||
iex> Mv.MembershipFees.CalendarCycles.calculate_cycle_end(~D[2024-03-01], :monthly)
|
||||
~D[2024-03-31]
|
||||
|
||||
iex> calculate_cycle_end(~D[2024-02-01], :monthly)
|
||||
iex> Mv.MembershipFees.CalendarCycles.calculate_cycle_end(~D[2024-02-01], :monthly)
|
||||
~D[2024-02-29]
|
||||
|
||||
iex> calculate_cycle_end(~D[2024-01-01], :quarterly)
|
||||
iex> Mv.MembershipFees.CalendarCycles.calculate_cycle_end(~D[2024-01-01], :quarterly)
|
||||
~D[2024-03-31]
|
||||
|
||||
iex> calculate_cycle_end(~D[2024-01-01], :half_yearly)
|
||||
iex> Mv.MembershipFees.CalendarCycles.calculate_cycle_end(~D[2024-01-01], :half_yearly)
|
||||
~D[2024-06-30]
|
||||
|
||||
iex> calculate_cycle_end(~D[2024-01-01], :yearly)
|
||||
iex> Mv.MembershipFees.CalendarCycles.calculate_cycle_end(~D[2024-01-01], :yearly)
|
||||
~D[2024-12-31]
|
||||
"""
|
||||
@spec calculate_cycle_end(Date.t(), interval()) :: Date.t()
|
||||
|
|
@ -123,16 +143,16 @@ defmodule Mv.MembershipFees.CalendarCycles do
|
|||
|
||||
## Examples
|
||||
|
||||
iex> next_cycle_start(~D[2024-01-01], :monthly)
|
||||
iex> Mv.MembershipFees.CalendarCycles.next_cycle_start(~D[2024-01-01], :monthly)
|
||||
~D[2024-02-01]
|
||||
|
||||
iex> next_cycle_start(~D[2024-01-01], :quarterly)
|
||||
iex> Mv.MembershipFees.CalendarCycles.next_cycle_start(~D[2024-01-01], :quarterly)
|
||||
~D[2024-04-01]
|
||||
|
||||
iex> next_cycle_start(~D[2024-01-01], :half_yearly)
|
||||
iex> Mv.MembershipFees.CalendarCycles.next_cycle_start(~D[2024-01-01], :half_yearly)
|
||||
~D[2024-07-01]
|
||||
|
||||
iex> next_cycle_start(~D[2024-01-01], :yearly)
|
||||
iex> Mv.MembershipFees.CalendarCycles.next_cycle_start(~D[2024-01-01], :yearly)
|
||||
~D[2025-01-01]
|
||||
"""
|
||||
@spec next_cycle_start(Date.t(), interval()) :: Date.t()
|
||||
|
|
@ -143,63 +163,74 @@ defmodule Mv.MembershipFees.CalendarCycles do
|
|||
end
|
||||
|
||||
@doc """
|
||||
Checks if the cycle contains today's date.
|
||||
Checks if the cycle contains the given date.
|
||||
|
||||
## Parameters
|
||||
|
||||
- `cycle_start` - The start date of the cycle
|
||||
- `interval` - The interval type
|
||||
- `today` - The date to check (defaults to today's date)
|
||||
|
||||
## Returns
|
||||
|
||||
`true` if today is within the cycle, `false` otherwise.
|
||||
`true` if the given date is within the cycle, `false` otherwise.
|
||||
|
||||
## Examples
|
||||
|
||||
# Assuming today is 2024-03-15
|
||||
iex> current_cycle?(~D[2024-03-01], :monthly)
|
||||
iex> Mv.MembershipFees.CalendarCycles.current_cycle?(~D[2024-03-01], :monthly, ~D[2024-03-15])
|
||||
true
|
||||
|
||||
iex> current_cycle?(~D[2024-02-01], :monthly)
|
||||
iex> Mv.MembershipFees.CalendarCycles.current_cycle?(~D[2024-02-01], :monthly, ~D[2024-03-15])
|
||||
false
|
||||
|
||||
iex> Mv.MembershipFees.CalendarCycles.current_cycle?(~D[2024-03-01], :monthly, ~D[2024-03-01])
|
||||
true
|
||||
|
||||
iex> Mv.MembershipFees.CalendarCycles.current_cycle?(~D[2024-03-01], :monthly, ~D[2024-03-31])
|
||||
true
|
||||
"""
|
||||
@spec current_cycle?(Date.t(), interval()) :: boolean()
|
||||
def current_cycle?(cycle_start, interval) do
|
||||
today = Date.utc_today()
|
||||
@spec current_cycle?(Date.t(), interval(), Date.t()) :: boolean()
|
||||
def current_cycle?(cycle_start, interval, today) do
|
||||
cycle_end = calculate_cycle_end(cycle_start, interval)
|
||||
|
||||
Date.compare(cycle_start, today) in [:lt, :eq] and
|
||||
Date.compare(today, cycle_end) in [:lt, :eq]
|
||||
end
|
||||
|
||||
@spec current_cycle?(Date.t(), interval()) :: boolean()
|
||||
def current_cycle?(cycle_start, interval) do
|
||||
current_cycle?(cycle_start, interval, Date.utc_today())
|
||||
end
|
||||
|
||||
@doc """
|
||||
Checks if the cycle was just completed (ended yesterday or earlier, but is the most recent completed cycle).
|
||||
Checks if the cycle was just completed (ended before or on the given date, but is the most recent completed cycle).
|
||||
|
||||
## Parameters
|
||||
|
||||
- `cycle_start` - The start date of the cycle
|
||||
- `interval` - The interval type
|
||||
- `today` - The date to check against (defaults to today's date)
|
||||
|
||||
## Returns
|
||||
|
||||
`true` if the cycle ended yesterday or earlier and is the last completed cycle, `false` otherwise.
|
||||
`true` if the cycle ended before or on the given date and is the last completed cycle, `false` otherwise.
|
||||
|
||||
## Examples
|
||||
|
||||
# Assuming today is 2024-04-01 (cycle ended yesterday)
|
||||
iex> last_completed_cycle?(~D[2024-03-01], :monthly)
|
||||
iex> Mv.MembershipFees.CalendarCycles.last_completed_cycle?(~D[2024-03-01], :monthly, ~D[2024-04-01])
|
||||
true
|
||||
|
||||
# Assuming today is 2024-03-15 (cycle is still current)
|
||||
iex> last_completed_cycle?(~D[2024-03-01], :monthly)
|
||||
iex> Mv.MembershipFees.CalendarCycles.last_completed_cycle?(~D[2024-03-01], :monthly, ~D[2024-03-15])
|
||||
false
|
||||
|
||||
iex> Mv.MembershipFees.CalendarCycles.last_completed_cycle?(~D[2024-02-01], :monthly, ~D[2024-04-15])
|
||||
false
|
||||
"""
|
||||
@spec last_completed_cycle?(Date.t(), interval()) :: boolean()
|
||||
def last_completed_cycle?(cycle_start, interval) do
|
||||
today = Date.utc_today()
|
||||
@spec last_completed_cycle?(Date.t(), interval(), Date.t()) :: boolean()
|
||||
def last_completed_cycle?(cycle_start, interval, today) do
|
||||
cycle_end = calculate_cycle_end(cycle_start, interval)
|
||||
|
||||
# Cycle must have ended (yesterday or earlier)
|
||||
# Cycle must have ended (before or on the given date)
|
||||
case Date.compare(today, cycle_end) do
|
||||
:gt ->
|
||||
# Check if this is the most recent completed cycle
|
||||
|
|
@ -214,6 +245,11 @@ defmodule Mv.MembershipFees.CalendarCycles do
|
|||
end
|
||||
end
|
||||
|
||||
@spec last_completed_cycle?(Date.t(), interval()) :: boolean()
|
||||
def last_completed_cycle?(cycle_start, interval) do
|
||||
last_completed_cycle?(cycle_start, interval, Date.utc_today())
|
||||
end
|
||||
|
||||
# Private helper functions
|
||||
|
||||
defp monthly_cycle_start(date) do
|
||||
|
|
|
|||
|
|
@ -6,53 +6,9 @@ defmodule Mv.MembershipFees.CalendarCyclesTest do
|
|||
|
||||
alias Mv.MembershipFees.CalendarCycles
|
||||
|
||||
doctest Mv.MembershipFees.CalendarCycles
|
||||
|
||||
describe "calculate_cycle_start/3" do
|
||||
test "monthly: returns 1st of month for any date" do
|
||||
assert CalendarCycles.calculate_cycle_start(~D[2024-03-15], :monthly) == ~D[2024-03-01]
|
||||
assert CalendarCycles.calculate_cycle_start(~D[2024-03-31], :monthly) == ~D[2024-03-01]
|
||||
assert CalendarCycles.calculate_cycle_start(~D[2024-03-01], :monthly) == ~D[2024-03-01]
|
||||
end
|
||||
|
||||
test "quarterly: returns 1st of quarter (Jan/Apr/Jul/Oct)" do
|
||||
# Q1 (Jan-Mar)
|
||||
assert CalendarCycles.calculate_cycle_start(~D[2024-01-15], :quarterly) == ~D[2024-01-01]
|
||||
assert CalendarCycles.calculate_cycle_start(~D[2024-02-15], :quarterly) == ~D[2024-01-01]
|
||||
assert CalendarCycles.calculate_cycle_start(~D[2024-03-15], :quarterly) == ~D[2024-01-01]
|
||||
|
||||
# Q2 (Apr-Jun)
|
||||
assert CalendarCycles.calculate_cycle_start(~D[2024-04-15], :quarterly) == ~D[2024-04-01]
|
||||
assert CalendarCycles.calculate_cycle_start(~D[2024-05-15], :quarterly) == ~D[2024-04-01]
|
||||
assert CalendarCycles.calculate_cycle_start(~D[2024-06-15], :quarterly) == ~D[2024-04-01]
|
||||
|
||||
# Q3 (Jul-Sep)
|
||||
assert CalendarCycles.calculate_cycle_start(~D[2024-07-15], :quarterly) == ~D[2024-07-01]
|
||||
assert CalendarCycles.calculate_cycle_start(~D[2024-08-15], :quarterly) == ~D[2024-07-01]
|
||||
assert CalendarCycles.calculate_cycle_start(~D[2024-09-15], :quarterly) == ~D[2024-07-01]
|
||||
|
||||
# Q4 (Oct-Dec)
|
||||
assert CalendarCycles.calculate_cycle_start(~D[2024-10-15], :quarterly) == ~D[2024-10-01]
|
||||
assert CalendarCycles.calculate_cycle_start(~D[2024-11-15], :quarterly) == ~D[2024-10-01]
|
||||
assert CalendarCycles.calculate_cycle_start(~D[2024-12-15], :quarterly) == ~D[2024-10-01]
|
||||
end
|
||||
|
||||
test "half_yearly: returns 1st of half (Jan/Jul)" do
|
||||
# First half (Jan-Jun)
|
||||
assert CalendarCycles.calculate_cycle_start(~D[2024-01-15], :half_yearly) == ~D[2024-01-01]
|
||||
assert CalendarCycles.calculate_cycle_start(~D[2024-03-15], :half_yearly) == ~D[2024-01-01]
|
||||
assert CalendarCycles.calculate_cycle_start(~D[2024-06-15], :half_yearly) == ~D[2024-01-01]
|
||||
|
||||
# Second half (Jul-Dec)
|
||||
assert CalendarCycles.calculate_cycle_start(~D[2024-07-15], :half_yearly) == ~D[2024-07-01]
|
||||
assert CalendarCycles.calculate_cycle_start(~D[2024-09-15], :half_yearly) == ~D[2024-07-01]
|
||||
assert CalendarCycles.calculate_cycle_start(~D[2024-12-15], :half_yearly) == ~D[2024-07-01]
|
||||
end
|
||||
|
||||
test "yearly: returns 1st of January" do
|
||||
assert CalendarCycles.calculate_cycle_start(~D[2024-01-15], :yearly) == ~D[2024-01-01]
|
||||
assert CalendarCycles.calculate_cycle_start(~D[2024-06-15], :yearly) == ~D[2024-01-01]
|
||||
assert CalendarCycles.calculate_cycle_start(~D[2024-12-15], :yearly) == ~D[2024-01-01]
|
||||
end
|
||||
|
||||
test "uses reference_date when provided" do
|
||||
date = ~D[2024-03-15]
|
||||
reference = ~D[2024-05-20]
|
||||
|
|
@ -62,178 +18,76 @@ defmodule Mv.MembershipFees.CalendarCyclesTest do
|
|||
end
|
||||
end
|
||||
|
||||
describe "calculate_cycle_end/2" do
|
||||
test "monthly: returns last day of month" do
|
||||
# 31-day month
|
||||
assert CalendarCycles.calculate_cycle_end(~D[2024-03-01], :monthly) == ~D[2024-03-31]
|
||||
|
||||
# 30-day month
|
||||
assert CalendarCycles.calculate_cycle_end(~D[2024-04-01], :monthly) == ~D[2024-04-30]
|
||||
|
||||
# February in leap year
|
||||
assert CalendarCycles.calculate_cycle_end(~D[2024-02-01], :monthly) == ~D[2024-02-29]
|
||||
|
||||
# February in non-leap year
|
||||
assert CalendarCycles.calculate_cycle_end(~D[2023-02-01], :monthly) == ~D[2023-02-28]
|
||||
end
|
||||
|
||||
test "quarterly: returns last day of quarter" do
|
||||
# Q1: Jan-Mar -> Mar 31
|
||||
assert CalendarCycles.calculate_cycle_end(~D[2024-01-01], :quarterly) == ~D[2024-03-31]
|
||||
|
||||
# Q2: Apr-Jun -> Jun 30
|
||||
assert CalendarCycles.calculate_cycle_end(~D[2024-04-01], :quarterly) == ~D[2024-06-30]
|
||||
|
||||
# Q3: Jul-Sep -> Sep 30
|
||||
assert CalendarCycles.calculate_cycle_end(~D[2024-07-01], :quarterly) == ~D[2024-09-30]
|
||||
|
||||
# Q4: Oct-Dec -> Dec 31
|
||||
assert CalendarCycles.calculate_cycle_end(~D[2024-10-01], :quarterly) == ~D[2024-12-31]
|
||||
end
|
||||
|
||||
test "half_yearly: returns last day of half-year" do
|
||||
# First half: Jan-Jun -> Jun 30
|
||||
assert CalendarCycles.calculate_cycle_end(~D[2024-01-01], :half_yearly) == ~D[2024-06-30]
|
||||
|
||||
# Second half: Jul-Dec -> Dec 31
|
||||
assert CalendarCycles.calculate_cycle_end(~D[2024-07-01], :half_yearly) == ~D[2024-12-31]
|
||||
end
|
||||
|
||||
test "yearly: returns Dec 31" do
|
||||
assert CalendarCycles.calculate_cycle_end(~D[2024-01-01], :yearly) == ~D[2024-12-31]
|
||||
assert CalendarCycles.calculate_cycle_end(~D[2023-01-01], :yearly) == ~D[2023-12-31]
|
||||
end
|
||||
end
|
||||
|
||||
describe "next_cycle_start/2" do
|
||||
test "monthly: adds one month" do
|
||||
assert CalendarCycles.next_cycle_start(~D[2024-01-01], :monthly) == ~D[2024-02-01]
|
||||
assert CalendarCycles.next_cycle_start(~D[2024-02-01], :monthly) == ~D[2024-03-01]
|
||||
assert CalendarCycles.next_cycle_start(~D[2024-12-01], :monthly) == ~D[2025-01-01]
|
||||
end
|
||||
|
||||
test "quarterly: adds three months" do
|
||||
assert CalendarCycles.next_cycle_start(~D[2024-01-01], :quarterly) == ~D[2024-04-01]
|
||||
assert CalendarCycles.next_cycle_start(~D[2024-04-01], :quarterly) == ~D[2024-07-01]
|
||||
assert CalendarCycles.next_cycle_start(~D[2024-07-01], :quarterly) == ~D[2024-10-01]
|
||||
assert CalendarCycles.next_cycle_start(~D[2024-10-01], :quarterly) == ~D[2025-01-01]
|
||||
end
|
||||
|
||||
test "half_yearly: adds six months" do
|
||||
assert CalendarCycles.next_cycle_start(~D[2024-01-01], :half_yearly) == ~D[2024-07-01]
|
||||
assert CalendarCycles.next_cycle_start(~D[2024-07-01], :half_yearly) == ~D[2025-01-01]
|
||||
end
|
||||
|
||||
test "yearly: adds one year" do
|
||||
assert CalendarCycles.next_cycle_start(~D[2024-01-01], :yearly) == ~D[2025-01-01]
|
||||
assert CalendarCycles.next_cycle_start(~D[2023-01-01], :yearly) == ~D[2024-01-01]
|
||||
end
|
||||
end
|
||||
|
||||
describe "current_cycle?/2" do
|
||||
test "returns true when today is within cycle" do
|
||||
today = Date.utc_today()
|
||||
cycle_start = CalendarCycles.calculate_cycle_start(today, :monthly)
|
||||
|
||||
assert CalendarCycles.current_cycle?(cycle_start, :monthly) == true
|
||||
end
|
||||
|
||||
test "returns true when today equals cycle start" do
|
||||
today = Date.utc_today()
|
||||
cycle_start = today
|
||||
|
||||
# For monthly, if today is the 1st, it's the cycle start
|
||||
if today.day == 1 do
|
||||
assert CalendarCycles.current_cycle?(cycle_start, :monthly) == true
|
||||
end
|
||||
end
|
||||
|
||||
test "returns true when today equals cycle end" do
|
||||
today = Date.utc_today()
|
||||
cycle_start = CalendarCycles.calculate_cycle_start(today, :monthly)
|
||||
cycle_end = CalendarCycles.calculate_cycle_end(cycle_start, :monthly)
|
||||
|
||||
# If today is the last day of the month, it's the cycle end
|
||||
if today == cycle_end do
|
||||
assert CalendarCycles.current_cycle?(cycle_start, :monthly) == true
|
||||
end
|
||||
end
|
||||
|
||||
test "returns false when today is before cycle start" do
|
||||
future_date = Date.add(Date.utc_today(), 35)
|
||||
cycle_start = CalendarCycles.calculate_cycle_start(future_date, :monthly)
|
||||
|
||||
assert CalendarCycles.current_cycle?(cycle_start, :monthly) == false
|
||||
end
|
||||
|
||||
test "returns false when today is after cycle end" do
|
||||
past_date = Date.add(Date.utc_today(), -35)
|
||||
cycle_start = CalendarCycles.calculate_cycle_start(past_date, :monthly)
|
||||
|
||||
assert CalendarCycles.current_cycle?(cycle_start, :monthly) == false
|
||||
end
|
||||
describe "current_cycle?/3" do
|
||||
# Basic examples are covered by doctests
|
||||
|
||||
test "works for all interval types" do
|
||||
today = Date.utc_today()
|
||||
today = ~D[2024-03-15]
|
||||
|
||||
for interval <- [:monthly, :quarterly, :half_yearly, :yearly] do
|
||||
cycle_start = CalendarCycles.calculate_cycle_start(today, interval)
|
||||
result = CalendarCycles.current_cycle?(cycle_start, interval)
|
||||
result = CalendarCycles.current_cycle?(cycle_start, interval, today)
|
||||
|
||||
assert result == true, "Expected current cycle for #{interval} with start #{cycle_start}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "last_completed_cycle?/2" do
|
||||
test "returns true when cycle ended yesterday" do
|
||||
yesterday = Date.add(Date.utc_today(), -1)
|
||||
cycle_start = CalendarCycles.calculate_cycle_start(yesterday, :monthly)
|
||||
cycle_end = CalendarCycles.calculate_cycle_end(cycle_start, :monthly)
|
||||
|
||||
# Only test if yesterday was actually the cycle end
|
||||
if yesterday == cycle_end do
|
||||
assert CalendarCycles.last_completed_cycle?(cycle_start, :monthly) == true
|
||||
end
|
||||
end
|
||||
|
||||
test "returns false when cycle is still current" do
|
||||
describe "current_cycle?/2 wrapper" do
|
||||
test "calls current_cycle?/3 with Date.utc_today()" do
|
||||
today = Date.utc_today()
|
||||
cycle_start = CalendarCycles.calculate_cycle_start(today, :monthly)
|
||||
|
||||
assert CalendarCycles.last_completed_cycle?(cycle_start, :monthly) == false
|
||||
end
|
||||
# This test verifies the wrapper works, but uses actual today
|
||||
# The real testing happens in current_cycle?/3 tests above
|
||||
result = CalendarCycles.current_cycle?(cycle_start, :monthly)
|
||||
|
||||
test "returns false when cycle is in the future" do
|
||||
future_date = Date.add(Date.utc_today(), 35)
|
||||
cycle_start = CalendarCycles.calculate_cycle_start(future_date, :monthly)
|
||||
|
||||
assert CalendarCycles.last_completed_cycle?(cycle_start, :monthly) == false
|
||||
assert result == true
|
||||
end
|
||||
end
|
||||
|
||||
describe "last_completed_cycle?/3" do
|
||||
# Basic examples are covered by doctests
|
||||
|
||||
test "returns false when next cycle has also ended" do
|
||||
# Use a date from two cycles ago
|
||||
past_date = Date.add(Date.utc_today(), -65)
|
||||
cycle_start = CalendarCycles.calculate_cycle_start(past_date, :monthly)
|
||||
# Two cycles ago: cycle ended, but next cycle also ended
|
||||
today = ~D[2024-05-15]
|
||||
cycle_start = ~D[2024-03-01]
|
||||
# Cycle ended 2024-03-31, next cycle ended 2024-04-30, today is 2024-05-15
|
||||
|
||||
assert CalendarCycles.last_completed_cycle?(cycle_start, :monthly) == false
|
||||
assert CalendarCycles.last_completed_cycle?(cycle_start, :monthly, today) == false
|
||||
end
|
||||
|
||||
test "works correctly for quarterly intervals" do
|
||||
# Test with a known past quarter
|
||||
# Q1 2024 ended on 2024-03-31
|
||||
# Q2 2024 ends on 2024-06-30
|
||||
# Today is 2024-04-15 (after Q1 ended, before Q2 ended)
|
||||
today = ~D[2024-04-15]
|
||||
past_quarter_start = ~D[2024-01-01]
|
||||
|
||||
assert CalendarCycles.last_completed_cycle?(past_quarter_start, :quarterly, today) == true
|
||||
end
|
||||
|
||||
test "returns false when cycle ended on the given date" do
|
||||
# Cycle ends on today, so it's still current, not completed
|
||||
today = ~D[2024-03-31]
|
||||
cycle_start = ~D[2024-03-01]
|
||||
|
||||
assert CalendarCycles.last_completed_cycle?(cycle_start, :monthly, today) == false
|
||||
end
|
||||
end
|
||||
|
||||
describe "last_completed_cycle?/2 wrapper" do
|
||||
test "calls last_completed_cycle?/3 with Date.utc_today()" do
|
||||
today = Date.utc_today()
|
||||
cycle_start = CalendarCycles.calculate_cycle_start(today, :monthly)
|
||||
|
||||
if Date.compare(today, CalendarCycles.calculate_cycle_end(past_quarter_start, :quarterly)) ==
|
||||
:gt do
|
||||
# Check if next quarter hasn't ended yet
|
||||
next_quarter_start = CalendarCycles.next_cycle_start(past_quarter_start, :quarterly)
|
||||
next_quarter_end = CalendarCycles.calculate_cycle_end(next_quarter_start, :quarterly)
|
||||
# This test verifies the wrapper works, but uses actual today
|
||||
# The real testing happens in last_completed_cycle?/3 tests above
|
||||
result = CalendarCycles.last_completed_cycle?(cycle_start, :monthly)
|
||||
|
||||
if Date.compare(today, next_quarter_end) in [:lt, :eq] do
|
||||
assert CalendarCycles.last_completed_cycle?(past_quarter_start, :quarterly) == true
|
||||
end
|
||||
end
|
||||
# Result depends on actual today, so we just verify it's a boolean
|
||||
assert is_boolean(result)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue