diff options
| author | Somhairle H. Marisol <[email protected]> | 2026-09-21 11:44:07 +0800 |
|---|---|---|
| committer | Somhairle H. Marisol <[email protected]> | 2026-09-21 11:44:07 +0800 |
| commit | 205e2a592bf781f08fe23f3577cdfe74ff9a076a (patch) | |
| tree | 05aad6f87ca8f6c78fbc2d4c4d0d89356439936a /tests/FundLab.Web.Tests | |
| parent | b8811922f4e167d50efb8817da35a12d8a9d653b (diff) | |
| download | fund-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')
| -rw-r--r-- | tests/FundLab.Web.Tests/BoundaryTests.fs | 72 |
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) |
