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)