XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet

XFEEDToolBox

【WinUI】XFE精英危险工具箱

公开
关注 0 Fork 0 Star 0
UTF-8
using System.Text.Json;
using XFEEDToolBox.Core.Execution;
using XFEEDToolBox.Core.Models;
using XFEEDToolBox.Core.Navigation;
using XFEExtension.NetCore.XUnit.Attributes;

namespace XFEEDToolBox.Test;

[TestFixture]public sealed class AutomaticTradeSafetyRecoveryTests
{
    private static readonly DateTimeOffset Now=DateTimeOffset.UtcNow;
    private static PlayerState Ship=>new(){CommanderId="safety-test",CurrentShipId=8,CargoCapacity=1200,CargoCount=0,
        InGame=true,LoadGameAt=Now.AddHours(-1),Docked=true,MarketId=1,CargoReliable=true,CreditsReliable=true,StatusReadAt=Now,
        Ships=[new(){ShipId=8,HullHealth=.8}]};
    [Test]public void SafeDockAndFreshContinuousHealthMustRemainStableForTenSeconds()
    {
        var gate=new AutomaticTradeSafetyGate(Ship);
        Check.That(!gate.Observe(Ship,Now,true));Check.That(!gate.Observe(Ship with{StatusReadAt=Now.AddSeconds(9)},Now.AddSeconds(9),true));
        Check.That(gate.Observe(Ship with{StatusReadAt=Now.AddSeconds(10)},Now.AddSeconds(10),true));
    }
    [Test]public void FlightThreatDamageHealthGapAndStaleStatusNeverAuthorizeResume()
    {
        foreach(var unsafeState in new[]{Ship with{Docked=false},Ship with{Navigation=new(){ExternalThreat=true}},Ship with{CargoReliable=false},
            Ship with{CreditsReliable=false},Ship with{StatusReadAt=Now.AddSeconds(-3)},Ship with{MarketId=null},
            Ship with{Ships=[new(){ShipId=8,HullHealth=0}]},Ship with{InGame=false}})
        {
            var gate=new AutomaticTradeSafetyGate(Ship);Check.That(!gate.Observe(Ship,Now,true));
            Check.That(!gate.Observe(unsafeState,Now.AddSeconds(10),true));
            Check.That(!gate.Observe(Ship with{StatusReadAt=Now.AddSeconds(11)},Now.AddSeconds(11),true));
        }
        var health=new AutomaticTradeSafetyGate(Ship);Check.That(!health.Observe(Ship,Now,true));
        Check.That(!health.Observe(Ship with{StatusReadAt=Now.AddSeconds(10)},Now.AddSeconds(10),false));
    }
    [Test]public void IdentitySessionOrCapacityChangesRemainTerminalAndCannotBeRearmed()
    {
        foreach(var foreign in new[]{Ship with{CommanderId="other"},Ship with{CurrentShipId=9},Ship with{CargoCapacity=2000},Ship with{LoadGameAt=Now}})
            Check.Throws<InvalidOperationException>(()=>new AutomaticTradeSafetyGate(Ship).Observe(foreign,Now,true));
        Check.That(!FlightThreatPauseException.Present(new OperationCanceledException("manual cancel")));
        Check.That(!FlightThreatPauseException.Present(new InvalidOperationException("人工接管")));
        Check.That(FlightThreatPauseException.Present(new InvalidOperationException("navigation wrapper",new FlightThreatPauseException())));
    }
    [Test]public void MovingToAnotherDockResetsTheStabilityWindow()
    {
        var gate=new AutomaticTradeSafetyGate(Ship);gate.Observe(Ship,Now,true);
        Check.That(!gate.Observe(Ship with{MarketId=2,StatusReadAt=Now.AddSeconds(10)},Now.AddSeconds(10),true));
        Check.That(gate.Observe(Ship with{MarketId=2,StatusReadAt=Now.AddSeconds(20)},Now.AddSeconds(20),true));
    }
    [Test]public void StableHistoricalHullInFlightStillNeedsFreshContinuousAuthorization()
    {
        var flying=Ship with{Docked=false,MarketId=null};var gate=new AutomaticTradeSafetyGate(flying){RequireDocked=false};
        Check.That(!gate.Observe(flying,Now,false));var at=Now.AddSeconds(10);
        Check.That(!gate.Observe(flying with{StatusReadAt=at},at,true));at=Now.AddSeconds(20);
        Check.That(gate.Observe(flying with{StatusReadAt=at},at,true));
        Check.That(!gate.Observe(flying with{StatusReadAt=at},at,false));
    }
    [Test]public void RepeatAttackBackoffPersistsAndDuplicateJournalRowsDoNotMultiplyIt()
    {
        var first=new TradeSourceThreat(42,"Station",Now.AddHours(-2),Now.AddHours(-1));
        var second=TradeSourceThreat.Merge(first,new(42,"Station",Now,Now.AddHours(1)));
        Check.Equal(2,second.Incidents);Check.Equal(Now.AddHours(24),second.AvoidUntil);
        Check.Equal(2,TradeSourceThreat.Merge(second,second with{AttackedAt=Now.AddSeconds(1)}).Incidents);
        var third=TradeSourceThreat.Merge(second,new(42,"Station",Now.AddHours(25),Now.AddHours(26)));
        Check.Equal(3,third.Incidents);Check.Equal(Now.AddHours(97),third.AvoidUntil);
    }
    [Test]public void DepartureAttackIsAttributedToTheStationActuallyLeft()
    {
        using var folder=new TemporaryDirectory();var file=Path.Combine(folder.Path,"Journal.departure.log");
        File.WriteAllLines(file,new object[]{new{timestamp=Now.AddMinutes(-3),@event="LoadGame",FID="safety-test",ShipID=8},
            new{timestamp=Now.AddMinutes(-2),@event="Undocked",MarketID=42,StationName="Actual origin"},
            new{timestamp=Now.AddMinutes(-1),@event="UnderAttack",Target="You"}}.Select(value=>JsonSerializer.Serialize(value)));
        var rows=TradeSourceThreatJournal.Read(folder.Path,Ship,Now);Check.Equal(1,rows.Count);Check.Equal(42L,rows[0].MarketId);
    }
    private static void Journal(string path,string commander="safety-test",long ship=8,string target="You",bool afterJump=false)
    {
        var rows=new List<object>{new{timestamp=Now.AddHours(-2),@event="LoadGame",FID=commander,ShipID=ship},
            new{timestamp=Now.AddMinutes(-10),@event="DockingGranted",MarketID=42,StationName="Attacked supply"}};
        if(afterJump)rows.Add(new{timestamp=Now.AddMinutes(-9),@event="FSDJump",StarSystem="Other"});
        rows.Add(new{timestamp=Now.AddMinutes(-8),@event="UnderAttack",Target=target});
        File.WriteAllLines(path,rows.Select(r=>JsonSerializer.Serialize(r)));
    }
    [Test]public void OriginalApproachAttackIsScopedToOurCommanderShipAndStation()
    {
        using var dir=new TemporaryDirectory();Journal(Path.Combine(dir.Path,"Journal.test.log"));
        var rows=TradeSourceThreatJournal.Read(dir.Path,Ship,Now);Check.Equal(1,rows.Count);Check.Equal(42L,rows[0].MarketId);
        Check.Equal(Now.AddMinutes(52),rows[0].AvoidUntil);
        Check.Equal(0,TradeSourceThreatJournal.Read(dir.Path,Ship with{CommanderId="other"},Now).Count);
        Check.Equal(0,TradeSourceThreatJournal.Read(dir.Path,Ship with{CurrentShipId=9},Now).Count);
        Check.Equal(0,TradeSourceThreatJournal.Read(dir.Path,Ship,Now.AddHours(2)).Count);
    }
    [Test]public void DifferentTargetAndAnOldStationPermissionDoNotQuarantineAnUnrelatedMarket()
    {
        using var dir=new TemporaryDirectory();var file=Path.Combine(dir.Path,"Journal.test.log");Journal(file,target:"Fighter");
        Check.Equal(0,TradeSourceThreatJournal.Read(dir.Path,Ship,Now).Count);Journal(file,afterJump:true);
        Check.Equal(0,TradeSourceThreatJournal.Read(dir.Path,Ship,Now).Count);
    }
}