From 6abd4c206fbfe8369b3fde4c905619d08f13c5d1 Mon Sep 17 00:00:00 2001 From: "Somhairle H. Marisol" Date: Tue, 22 Sep 2026 05:23:52 +0800 Subject: Add SIP plan pause/resume and rebalance plan archive lifecycle (3d-24) --- tests/FundLab.Api.Tests/RebalanceArchiveTests.fs | 149 +++++++++++++++++++++++ 1 file changed, 149 insertions(+) create mode 100644 tests/FundLab.Api.Tests/RebalanceArchiveTests.fs (limited to 'tests/FundLab.Api.Tests/RebalanceArchiveTests.fs') diff --git a/tests/FundLab.Api.Tests/RebalanceArchiveTests.fs b/tests/FundLab.Api.Tests/RebalanceArchiveTests.fs new file mode 100644 index 0000000..ebb238b --- /dev/null +++ b/tests/FundLab.Api.Tests/RebalanceArchiveTests.fs @@ -0,0 +1,149 @@ +namespace FundLab.Api.Tests + +open System +open Xunit +open FundLab.Api + +[] +type RebalanceArchiveTests(fixture: PostgresFixture) = + let sharedRepository = + lazy + let value = FundRepository(fixture.ConnectionString) + value.EnsureSchema() + value + + let repository () = sharedRepository.Value + + let seedInstrument () = + let code = Random.Shared.Next(0, 1000000).ToString("D6") + + let payload = + { + Source = "akshare" + SourceRevision = "akshare-test/eastmoney" + CollectedAt = DateTimeOffset(2026, 9, 21, 8, 0, 0, TimeSpan.Zero) + Instruments = [ { Code = code; Name = "再平衡归档测试基金"; FundType = None } ] + } + + repository().UpsertInstruments(payload, "rebalance-archive-test-hash") + code + + let createFund (initialCash: decimal) = + let command = + { + Name = "再平衡归档测试 FOF" + InitialCash = initialCash + InitialUnitNav = 1.00000000m + IsSynthetic = true + } + + let key = fixture.Key(sprintf "rebalance-archive-fund-%s" (Guid.NewGuid().ToString("N"))) + + match repository().CreateFund(key, command) with + | FundWriteResult.Created fund -> fund.Id + | other -> failwithf "unexpected fund creation result: %A" other + + let app () = App.createApplication (repository ()) + + let createPlan (fundId: Guid) (code: string) = + let key = fixture.Key(sprintf "rebalance-archive-plan-%s" (Guid.NewGuid().ToString("N"))) + + match + repository().CreateRebalancePlan(key, fundId, { Targets = [ { InstrumentCode = code; TargetPercent = 100.00m } ] }) + with + | RebalanceWriteResult.RebalancePlanCreated plan -> plan.Id + | other -> failwithf "unexpected rebalance plan result: %A" other + + let archiveViaApi fundId planId = + PersistenceTestHelpers.invoke + (app ()) + "POST" + (sprintf "/api/funds/%O/rebalance/plans/%O/archive" fundId planId) + [ "Authorization", "Bearer test-token" ] + "{}" + + let previewViaApi fundId planId = + PersistenceTestHelpers.invoke + (app ()) + "GET" + (sprintf "/api/funds/%O/rebalance/plans/%O/preview" fundId planId) + [ "Authorization", "Bearer test-token" ] + "" + + let executeViaApi fundId planId = + PersistenceTestHelpers.invoke + (app ()) + "POST" + (sprintf "/api/funds/%O/rebalance/plans/%O/execute" fundId planId) + [ "Authorization", "Bearer test-token" ] + "{}" + + let plansViaApi fundId = + PersistenceTestHelpers.invoke + (app ()) + "GET" + (sprintf "/api/funds/%O/rebalance/plans" fundId) + [ "Authorization", "Bearer test-token" ] + "" + + [] + member _.``archiving an active plan marks it archived in the plan list``() = + let fundId = createFund 1000.00m + let code = seedInstrument () + let planId = createPlan fundId code + + let status, body = archiveViaApi fundId planId + Assert.Equal(200, status) + Assert.Contains("\"status\":\"archived\"", body) + + let _, plansBody = plansViaApi fundId + Assert.Contains("\"status\":\"archived\"", plansBody) + + [] + member _.``archived plan cannot be previewed``() = + let fundId = createFund 1000.00m + let code = seedInstrument () + let planId = createPlan fundId code + + let _, _ = archiveViaApi fundId planId + let status, body = previewViaApi fundId planId + + Assert.Equal(409, status) + Assert.Contains("PLAN_ARCHIVED", body) + + [] + member _.``archived plan cannot be executed``() = + let fundId = createFund 1000.00m + let code = seedInstrument () + let planId = createPlan fundId code + + let _, _ = archiveViaApi fundId planId + let status, body = executeViaApi fundId planId + + Assert.Equal(409, status) + Assert.Contains("PLAN_ARCHIVED", body) + + [] + member _.``archiving an already archived plan is idempotent``() = + let fundId = createFund 1000.00m + let code = seedInstrument () + let planId = createPlan fundId code + + let firstStatus, firstBody = archiveViaApi fundId planId + Assert.Equal(200, firstStatus) + Assert.Contains("\"status\":\"archived\"", firstBody) + + let secondStatus, secondBody = archiveViaApi fundId planId + Assert.Equal(200, secondStatus) + Assert.Contains("\"status\":\"archived\"", secondBody) + + [] + member _.``archiving a plan owned by another fund answers 404``() = + let fundId = createFund 1000.00m + let otherFundId = createFund 1000.00m + let code = seedInstrument () + let planId = createPlan fundId code + + let status, body = archiveViaApi otherFundId planId + Assert.Equal(404, status) + Assert.Contains("PLAN_NOT_FOUND", body) -- cgit v1.2.3