summaryrefslogtreecommitdiff
path: root/tests/FundLab.Web.Tests/BoundaryTests.fs
diff options
context:
space:
mode:
Diffstat (limited to 'tests/FundLab.Web.Tests/BoundaryTests.fs')
-rw-r--r--tests/FundLab.Web.Tests/BoundaryTests.fs86
1 files changed, 86 insertions, 0 deletions
diff --git a/tests/FundLab.Web.Tests/BoundaryTests.fs b/tests/FundLab.Web.Tests/BoundaryTests.fs
index 5ab327d..2435f32 100644
--- a/tests/FundLab.Web.Tests/BoundaryTests.fs
+++ b/tests/FundLab.Web.Tests/BoundaryTests.fs
@@ -803,3 +803,89 @@ module SipBoundaryTests =
let staleList, _ = update (SipPlansReadCompleted(1, [| sampleSipRaw () |])) { model with sipReadSeq = 2 }
Assert.Equal(1, staleList.sipPlans.Length)
+
+module RebalanceBoundaryTests =
+
+ open Xunit
+
+ let sampleFundInfo () : FundSummary =
+ {
+ id = "3fa85f64-5717-4562-b3fc-2c963f66afa6"
+ name = "再平衡测试"
+ currency = "CNY"
+ initialCash = "20000.00"
+ initialUnitNav = "1.00000000"
+ isSynthetic = false
+ availableCash = "19500.00"
+ reservedCash = "0.00"
+ status = "active"
+ }
+
+ let sampleRebalanceRaw () : RawRebalancePlan =
+ {
+ id = "bb2c3d4e-0000-0000-0000-000000000003"
+ fundId = "3fa85f64-5717-4562-b3fc-2c963f66afa6"
+ targets =
+ [|
+ { instrumentCode = "000001"; targetPercent = "60.00" }
+ { instrumentCode = "000002"; targetPercent = "40.00" }
+ |]
+ status = "active"
+ createdAt = "2026-09-21T10:00:00+00:00"
+ }
+
+ let sampleExecutionRaw () : RawRebalanceExecution =
+ {
+ planId = "cc3d4e5f-0000-0000-0000-000000000004"
+ runDate = "2026-09-21"
+ outcomes =
+ [|
+ {
+ instrumentCode = "000002"
+ action = "buy"
+ amount = "4800.00"
+ status = "succeeded"
+ orderId = box "9d8c7b6a-0000-0000-0000-000000000009"
+ pendingReason = null
+ }
+ |]
+ }
+
+ [<Fact>]
+ let ``rebalance decode keeps decimal strings per target`` () =
+ let decoded = Api.decodeRebalancePlan (sampleRebalanceRaw ())
+
+ Assert.Equal(2, decoded.targets.Length)
+ Assert.Equal("000001", decoded.targets[0].instrumentCode)
+ Assert.Equal("60.00", decoded.targets[0].targetPercent)
+ Assert.Equal("40.00", decoded.targets[1].targetPercent)
+ Assert.Equal("active", decoded.status)
+
+ [<Fact>]
+ let ``rebalance execution decodes outcome fields and refreshes orders`` () =
+ let started, _ =
+ update (RebalanceExecuteRequested "cc3d4e5f-0000-0000-0000-000000000004")
+ { init () with token = "qa-token"; createdFund = Some(sampleFundInfo ()); rebalExecuteSeq = 0 }
+
+ Assert.Equal(1, started.rebalExecuteSeq)
+ Assert.True(started.rebalExecuteInFlight)
+
+ let executed, _ = update (RebalanceExecuted(1, sampleExecutionRaw ())) started
+
+ Assert.False(executed.rebalExecuteInFlight)
+ let result = executed.rebalResult.Value
+ Assert.Equal(1, result.Length)
+ Assert.Equal("buy", result[0].action)
+ Assert.Equal("4800.00", result[0].amount)
+ Assert.Equal("9d8c7b6a-0000-0000-0000-000000000009", result[0].orderId.Value)
+ Assert.Equal(None, result[0].pendingReason)
+
+ [<Fact>]
+ let ``stale rebalance executions are ignored`` () =
+ let model =
+ { init () with token = "qa-token"; createdFund = Some(sampleFundInfo ()); rebalExecuteSeq = 3; rebalExecuteInFlight = true }
+
+ let stale, _ = update (RebalanceExecuted(2, sampleExecutionRaw ())) model
+
+ Assert.True(stale.rebalExecuteInFlight)
+ Assert.Equal(None, stale.rebalResult)