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)));
}
}
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)));
}
}