diff options
Diffstat (limited to 'tests/FundLab.Domain.Tests')
| -rw-r--r-- | tests/FundLab.Domain.Tests/DomainTests.fs | 61 |
1 files changed, 61 insertions, 0 deletions
diff --git a/tests/FundLab.Domain.Tests/DomainTests.fs b/tests/FundLab.Domain.Tests/DomainTests.fs index 46516d5..13be13f 100644 --- a/tests/FundLab.Domain.Tests/DomainTests.fs +++ b/tests/FundLab.Domain.Tests/DomainTests.fs @@ -560,3 +560,64 @@ module CapitalPolicyTests = let overflow = CapitalPolicy.settle CapitalPolicy.cashMaximum 0.01m Assert.Equal(Error "available cash exceeds database precision", overflow) + +module SipPolicyTests = + + open System + open Xunit + open FundLab.Domain + + let d (year: int) (month: int) (day: int) = DateOnly(year, month, day) + + [<Fact>] + let ``sip amount validation rejects non positive or imprecise values`` () = + Assert.Equal(Error "sip amount must be positive", SipPolicy.validateAmount 0m) + Assert.Equal(Error "sip amount must be positive", SipPolicy.validateAmount -200.00m) + Assert.Equal(Error "sip amount exceeds cash precision", SipPolicy.validateAmount 200.005m) + Assert.Equal(Ok(), SipPolicy.validateAmount 200.00m) + + [<Fact>] + let ``frequency parsing roundtrips through the canonical text`` () = + Assert.Equal(Some Weekly, SipPolicy.parseFrequency "weekly") + Assert.Equal(Some BiWeekly, SipPolicy.parseFrequency "biweekly") + Assert.Equal(Some Monthly, SipPolicy.parseFrequency "monthly") + Assert.Equal(None, SipPolicy.parseFrequency "daily") + Assert.Equal(None, SipPolicy.parseFrequency null) + + for frequency in [ Weekly; BiWeekly; Monthly ] do + Assert.Equal(Some frequency, SipPolicy.parseFrequency (SipPolicy.frequencyText frequency)) + + [<Fact>] + let ``weekly and biweekly next trade dates step from the anchor`` () = + let anchor = d 2026 9 21 + + Assert.Equal(d 2026 9 21, SipPolicy.nextTradeDate Weekly anchor (d 2026 9 21)) + Assert.Equal(d 2026 9 28, SipPolicy.nextTradeDate Weekly anchor (d 2026 9 22)) + Assert.Equal(d 2026 10 5, SipPolicy.nextTradeDate BiWeekly anchor (d 2026 9 28)) + + [<Fact>] + let ``next trade dates roll off weekends`` () = + let anchor = d 2026 9 21 + + Assert.Equal(d 2026 9 28, SipPolicy.nextTradeDate Weekly anchor (d 2026 9 26)) + Assert.Equal(d 2026 9 28, SipPolicy.nextTradeDate Weekly anchor (d 2026 9 27)) + + [<Fact>] + let ``monthly next trade date clamps to month end and rolls off weekends`` () = + let anchor = d 2026 1 31 + + // 2026-01-31 is a Saturday: first run rolls to Monday 02-02 + Assert.Equal(d 2026 2 2, SipPolicy.nextTradeDate Monthly anchor anchor) + // February clamps day 31 to 28 (Saturday) and rolls to Monday 03-02 + Assert.Equal(d 2026 3 2, SipPolicy.nextTradeDate Monthly anchor (d 2026 2 1)) + Assert.Equal(d 2026 3 2, SipPolicy.nextTradeDate Monthly anchor (d 2026 2 28)) + Assert.Equal(d 2026 2 5, SipPolicy.nextTradeDate Monthly (d 2026 1 5) (d 2026 1 20)) + + [<Fact>] + let ``due flag and net amount define the executor contract without side effects`` () = + Assert.True(SipPolicy.isDue (d 2026 9 21) (d 2026 9 21)) + Assert.True(SipPolicy.isDue (d 2026 9 21) (d 2026 9 28)) + Assert.False(SipPolicy.isDue (d 2026 9 28) (d 2026 9 21)) + + Assert.Equal(Ok 200.00m, SipPolicy.executionNetAmount 200.00m) + Assert.Equal(Error "sip amount must be positive", SipPolicy.executionNetAmount 0m) |
