summaryrefslogtreecommitdiff
path: root/tests/FundLab.Web.Tests/BoundaryTests.fs
diff options
context:
space:
mode:
authorSomhairle H. Marisol <[email protected]>2026-09-21 11:44:07 +0800
committerSomhairle H. Marisol <[email protected]>2026-09-21 11:44:07 +0800
commit205e2a592bf781f08fe23f3577cdfe74ff9a076a (patch)
tree05aad6f87ca8f6c78fbc2d4c4d0d89356439936a /tests/FundLab.Web.Tests/BoundaryTests.fs
parentb8811922f4e167d50efb8817da35a12d8a9d653b (diff)
downloadfund-lab-205e2a592bf781f08fe23f3577cdfe74ff9a076a.tar.gz
Add additional capital deposit flow (3d-4)
Deposits credit a fund's available cash directly: Domain gains CapitalPolicy (positive, two-decimal, overflow-checked amounts plus a settle function that never touches units, NAV or realized P&L). Persistence records deposits in fund_capital_deposits with a fund_capital_idempotencies table, crediting cash and the deposit row atomically under the same replay/conflict semantics as orders. The API exposes POST /funds/{id}/capital/deposit and GET /funds/{id}/capital/deposits behind the bearer token and idempotency keys. The frontend adds an '追加资金' entry on the fund cash card with in-place balance refresh, and repeats of the same amount reuse the idempotency key so double submits credit cash exactly once. Browser QA extends the J-series (J12-J18): invalid-amount rejection without POST, single credited deposit with a 32-hex key, instant balance refresh, unchanged units/cost proving no realized-income impact, and same-key replay crediting only once.
Diffstat (limited to 'tests/FundLab.Web.Tests/BoundaryTests.fs')
-rw-r--r--tests/FundLab.Web.Tests/BoundaryTests.fs72
1 files changed, 72 insertions, 0 deletions
diff --git a/tests/FundLab.Web.Tests/BoundaryTests.fs b/tests/FundLab.Web.Tests/BoundaryTests.fs
index 015ed45..5226b65 100644
--- a/tests/FundLab.Web.Tests/BoundaryTests.fs
+++ b/tests/FundLab.Web.Tests/BoundaryTests.fs
@@ -635,3 +635,75 @@ module RedemptionBoundaryTests =
Assert.Equal("0.00000000", decoded.positions[0].reservedUnits)
Assert.Equal("5.00000000", decoded.positions[1].reservedUnits)
+
+module CapitalBoundaryTests =
+
+ let sampleFund () : FundSummary =
+ {
+ id = "3fa85f64-5717-4562-b3fc-2c963f66afa6"
+ name = "追加资金测试"
+ currency = "CNY"
+ initialCash = "10000.00"
+ initialUnitNav = "1.00000000"
+ isSynthetic = false
+ availableCash = "10000.00"
+ reservedCash = "0.00"
+ status = "active"
+ }
+
+ [<Fact>]
+ let ``capital deposit request requires fund token and positive amount`` () =
+ let noFund, _ = update CapitalDepositRequested { init () with token = "qa-token" }
+
+ Assert.Equal(0, noFund.capitalSeq)
+ Assert.False(noFund.capitalInFlight)
+
+ let noToken, _ =
+ update CapitalDepositRequested { init () with createdFund = Some(sampleFund ()) }
+
+ Assert.Equal(Some "请输入 API token", noToken.error)
+ Assert.Equal(0, noToken.capitalSeq)
+
+ let invalidAmount, _ =
+ update CapitalDepositRequested
+ { init () with token = "qa-token"; createdFund = Some(sampleFund ()); capitalAmount = "0.00" }
+
+ Assert.Equal(Some "追加金额必须是大于零的两位小数金额,例如 1000.00", invalidAmount.error)
+ Assert.Equal(0, invalidAmount.capitalSeq)
+
+ [<Fact>]
+ let ``valid deposit starts one attempt and completion refreshes the fund`` () =
+ let started, _ =
+ update CapitalDepositRequested
+ { init () with token = "qa-token"; createdFund = Some(sampleFund ()); capitalAmount = "500.00" }
+
+ Assert.Equal(1, started.capitalSeq)
+ Assert.True(started.capitalInFlight)
+ Assert.Equal("500.00", started.lastCapitalAttempt.Value.amount)
+ Assert.Equal(32, started.lastCapitalAttempt.Value.idempotencyKey.Length)
+
+ let completed, _ = update (CapitalDepositCompleted(1, "500.00")) started
+
+ Assert.False(completed.capitalInFlight)
+ Assert.Equal(Some "追加成功 + 500.00(现金余额已刷新)", completed.capitalNote)
+
+ [<Fact>]
+ let ``stale capital completion is ignored and token change clears the entry`` () =
+ let model =
+ { init () with
+ token = "qa-token"
+ createdFund = Some(sampleFund ())
+ capitalSeq = 4
+ capitalInFlight = true
+ lastCapitalAttempt = Some { idempotencyKey = "k"; amount = "500.00" } }
+
+ let stale, _ = update (CapitalDepositCompleted(3, "500.00")) model
+
+ Assert.True(stale.capitalInFlight)
+
+ let cleared, _ = update (TokenChanged "new-token") model
+
+ Assert.Equal("", cleared.capitalAmount)
+ Assert.Equal(None, cleared.createdFund)
+ Assert.False(cleared.capitalInFlight)
+ Assert.Equal(None, cleared.lastCapitalAttempt)