using System.Text.Json; using XFEEDToolBox.Core.Execution; using XFEEDToolBox.Core.Models; using XFEExtension.NetCore.XUnit.Attributes; namespace XFEEDToolBox.Test; [TestFixture] public sealed class HistoricalTradeCompletionTests { private static (TradeLeg Leg,PlayerState Player) Seed(string directory) { var started=DateTimeOffset.UtcNow.AddHours(-3); var buy=new ConfirmedTradeReceipt(Guid.NewGuid().ToString("N"),1,"water",10,20,1000,800,0,10,"F1",8,started.AddMinutes(-1),started.AddSeconds(10),true,true); var sale=new ConfirmedTradeReceipt(Guid.NewGuid().ToString("N"),2,"water",-10,40,800,1200,10,0,"F1",8,started.AddMinutes(-1),started.AddMinutes(3),true,true); var checkpoint=new TradeJourneyCheckpoint("F1",8,started,buy,sale);TradeJourneyCheckpoints.Save(Path.Combine(directory,"journey.json"),checkpoint); var leg=new TradeLeg{From=Check.Station(1),To=Check.Station(2),Orders=[new("water","Water",10,20,40)]}; File.WriteAllText(Path.Combine(directory,"leg.json"),JsonSerializer.Serialize(leg,ToolboxSettings.JsonOptions)); File.WriteAllText(Path.Combine(directory,"opening-wallet.json"),"{\"credits\":1000}"); using(var ledger=new TradeProfitLedger(Path.Combine(directory,"ledger"))) { ledger.RecordTrade(buy);ledger.RecordTrade(sale); var result=new NativeTradeJourneyResult(false,checkpoint,ledger.Snapshot,1000,null,"原始错误码 1; NativeTradeCommandDriver.MarketAsync",started.AddMinutes(3).AddSeconds(1)); File.WriteAllText(Path.Combine(directory,"result.json"),JsonSerializer.Serialize(result,ToolboxSettings.JsonOptions)); } return(leg,new(){InGame=true,Docked=true,MarketId=2,CommanderId="F1",CurrentShipId=8,CargoReliable=true,CreditsReliable=true,CargoCount=0,Credits=9000}); } [Test]public async Task AnAlreadyConfirmedSaleClosesAgainstItsHistoricalWalletWhilePreservingEveryOriginalByte() { using var temporary=new TemporaryDirectory();var (leg,player)=Seed(temporary.Path); var original=File.ReadAllBytes(temporary.File("result.json"));var checkpoint=File.ReadAllBytes(temporary.File("journey.json")); var proof=HistoricalTradeCompletion.ReadCandidate(temporary.Path,leg,player);Check.That(proof is not null);Check.Equal(200m,proof!.Original.Profit.NetProfit); Check.That(!HistoricalTradeCompletion.IsRecovered(temporary.Path,leg,player)); await HistoricalTradeCompletion.SealAsync(temporary.Path,leg,player,9000,CancellationToken.None); Check.That(HistoricalTradeCompletion.IsRecovered(temporary.Path,leg,player)); Check.That(original.SequenceEqual(File.ReadAllBytes(temporary.File("result.json"))));Check.That(checkpoint.SequenceEqual(File.ReadAllBytes(temporary.File("journey.json")))); Check.That(!HistoricalTradeCompletion.IsRecovered(temporary.Path,leg,player with{CurrentShipId=99})); File.AppendAllText(temporary.File("result.json")," ");Check.That(!HistoricalTradeCompletion.IsRecovered(temporary.Path,leg,player)); } [Test]public async Task ForeignOrdersUnclosedCashFlowAndMissingCurrentDockingCannotBeSealed() { using var temporary=new TemporaryDirectory();var (leg,player)=Seed(temporary.Path); Check.That(HistoricalTradeCompletion.ReadCandidate(temporary.Path,leg with{To=Check.Station(99)},player) is null); var rejected=false;try{await HistoricalTradeCompletion.SealAsync(temporary.Path,leg,player with{Docked=false},9000,CancellationToken.None);}catch(InvalidDataException){rejected=true;}Check.That(rejected); File.WriteAllText(temporary.File("opening-wallet.json"),"{\"credits\":999}");Check.That(HistoricalTradeCompletion.ReadCandidate(temporary.Path,leg,player) is null); } [Test]public void OnlyNonMutatingMarketFreshnessFailuresCanBeDeferredAfterAConfirmedTransaction() { const string response="""{"state":4,"error":1,"mutationsSent":0,"reconciliationRequired":false}"""; Check.That(NativeMarketSnapshotUnavailableException.IsFreshnessRejection(Check.Json(response))); foreach(var changed in new[]{response.Replace("\"mutationsSent\":0","\"mutationsSent\":1"),response.Replace("\"error\":1","\"error\":503"),response.Replace("false","true")}) Check.That(!NativeMarketSnapshotUnavailableException.IsFreshnessRejection(Check.Json(changed))); } }