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

XFEEDToolBox

【WinUI】XFE精英危险工具箱

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

namespace XFEEDToolBox.Test;

[TestFixture]
public class TravelPreparationTests
{
    private static PlayerState Player(long flags)=>new(){InGame=true,Status=new(flags,0,32,1,0,null,null,null,null,[]),StatusAt=DateTimeOffset.UtcNow,StatusReadAt=DateTimeOffset.UtcNow};
    private sealed class Channel:IGameInputChannel
    {
        public GameInputState Frame=new(1,42,DateTimeOffset.UtcNow,true,true,true);
        public readonly List<string> Writes=[];
        public Action? OnWrite;
        public Action? OnRead;
        public bool Refresh=true;
        public int RefusedReads,Reads;
        public string Refusal="Game input telemetry unavailable or stale";
        public Task ConnectAsync(int thread,CancellationToken token)=>Task.CompletedTask;
        public Task<GameInputState> ReadAsync(CancellationToken token){Reads++;OnRead?.Invoke();if(RefusedReads-->0)throw new InvalidOperationException(Refusal);if(Refresh)Frame=Frame with{Sequence=Frame.Sequence+1,ObservedAt=DateTimeOffset.UtcNow};return Task.FromResult(Frame);}
        public Task<long> SetAsync(GameInputState frame,string input,bool value,CancellationToken token)
        {
            Check.That(!value);Writes.Add(input);OnWrite?.Invoke();
            Frame=input switch{"hardpoints"=>Frame with{Hardpoints=value},"cargoScoop"=>Frame with{CargoScoop=value},"landingGear"=>Frame with{LandingGear=value},_=>throw new InvalidOperationException()};
            Frame=Frame with{AppliedGeneration=Writes.Count};return Task.FromResult((long)Writes.Count);
        }
    }
    [Test]public async Task RetractsEachDeployedInputAndWaitsForPhysicalStatusBeforeReturning()
    {
        var c=new Channel();var flags=(1L<<2)|(1L<<6)|(1L<<9);using var stop=new CancellationTokenSource(TimeSpan.FromSeconds(4));
        var task=TravelPreparation.EnsureRetractedAsync(c,()=>Player(flags),stop.Token,42);
        while(c.Writes.Count<3)await Task.Delay(10,stop.Token);
        await Task.Delay(120,stop.Token);Check.That(!task.IsCompleted);
        flags=0;await task;Check.Equal("hardpoints,cargoScoop,landingGear",string.Join(',',c.Writes));
    }
    [Test]public async Task AlreadyRetractedInputsAreNeverToggled()
    {
        var c=new Channel{Frame=new(1,42,DateTimeOffset.UtcNow,false,false,false)};
        await TravelPreparation.EnsureRetractedAsync(c,()=>Player(0),CancellationToken.None,42);Check.Equal(0,c.Writes.Count);
    }
    [Test]public async Task NewObserverWaitsForFreshInputWithoutSendingAnyToggle()
    {
        var c=new Channel{Frame=new(1,42,DateTimeOffset.UtcNow,false,false,false),RefusedReads=2};
        using var stop=new CancellationTokenSource(TimeSpan.FromSeconds(2));
        await TravelPreparation.EnsureRetractedAsync(c,()=>Player(0),stop.Token,42);
        Check.That(c.Reads>=4);Check.Equal(0,c.Writes.Count);
    }
    [Test]public async Task UnknownInputRefusalIsTerminalAndKnownWaitingCanBeCancelled()
    {
        var c=new Channel{RefusedReads=1,Refusal="Input observer ownership changed"};
        try{await TravelPreparation.EnsureRetractedAsync(c,()=>Player(0),CancellationToken.None,42);throw new Exception("Expected ownership refusal");}
        catch(InvalidOperationException){Check.Equal(1,c.Reads);Check.Equal(0,c.Writes.Count);}
        c.RefusedReads=int.MaxValue;c.Refusal="Game input telemetry unavailable or stale";
        using var stop=new CancellationTokenSource(150);
        try{await TravelPreparation.EnsureRetractedAsync(c,()=>Player(0),stop.Token,42);throw new Exception("Expected cancellation");}
        catch(OperationCanceledException){Check.Equal(0,c.Writes.Count);}
    }
    [Test]public async Task UnchangedStatusRemainsValidWhenFreshlyReadAndBelongsToTheCurrentSession()
    {
        var now=DateTimeOffset.UtcNow;
        var state=Player(0) with{StatusAt=now.AddMinutes(-5),LoadGameAt=now.AddHours(-1)};
        var c=new Channel{Frame=new(1,42,now,false,false,false)};
        await TravelPreparation.EnsureRetractedAsync(c,()=>state with{StatusReadAt=DateTimeOffset.UtcNow},CancellationToken.None,42);
        Check.Equal(0,c.Writes.Count);Check.Equal(now.AddMinutes(-5),state.StatusAt!.Value);
        Check.That(!TravelPreparation.StatusConfirmed(state with{StatusReadAt=now.AddSeconds(-3)},now));
        Check.That(!TravelPreparation.StatusConfirmed(state with{LoadGameAt=now.AddMinutes(-1)},now));
        Check.That(!TravelPreparation.StatusConfirmed(state with{StatusAt=now.AddSeconds(6)},now));
    }
    [Test]public async Task MissingSnapshotReadWaitsWithoutWritingAndCanBeCancelled()
    {
        var c=new Channel();using var stop=new CancellationTokenSource(150);
        try{await TravelPreparation.EnsureRetractedAsync(c,()=>Player(0) with{StatusReadAt=null},stop.Token,42);throw new Exception("Expected cancellation");}
        catch(OperationCanceledException){Check.Equal(0,c.Writes.Count);}
    }
    [Test]public async Task CancellationDuringRetractionCannotProceedToCharge()
    {
        var c=new Channel();using var stop=new CancellationTokenSource();c.OnWrite=stop.Cancel;
        try{await TravelPreparation.EnsureRetractedAsync(c,()=>Player(1L<<6),stop.Token,42);throw new InvalidOperationException("Expected cancellation");}
        catch(OperationCanceledException){Check.Equal(1,c.Writes.Count);}
    }
    [Test]public async Task WrongShipAndStaleInputAreRejectedBeforeWriting()
    {
        var c=new Channel();
        try{await TravelPreparation.EnsureRetractedAsync(c,()=>Player(1L<<6),CancellationToken.None,99);throw new Exception("Expected ship check");}catch(InvalidOperationException){}
        Check.Equal(0,c.Writes.Count);c.Refresh=false;c.Frame=c.Frame with{ObservedAt=DateTimeOffset.UtcNow.AddSeconds(-1)};
        try{await TravelPreparation.EnsureRetractedAsync(c,()=>Player(1L<<6),CancellationToken.None,42);throw new Exception("Expected freshness check");}catch(FlightTelemetryUnavailableException){}
        Check.Equal(0,c.Writes.Count);
    }
    private static readonly SystemNavigationTarget Arrival=new(1,10,"Elysian Fields",SystemTargetKind.Station,20);
    private static SystemFlightFrame Landing=>new(){Sequence=1,ObservedAt=DateTimeOffset.UtcNow,InGame=true,GeometryVerified=true,ShipEntity=42,ReferenceWorld=10,
        SelectedTarget=Arrival,SystemAddress=1,Mode=LocalFlightMode.Normal,Assists=new(false,true,false),DockingAssist=NativeAssistState.Engaged,
        Clearance=new(20,DockingPermission.Granted,1,DateTimeOffset.UtcNow,DateTimeOffset.UtcNow.AddMilliseconds(300))};
    [Test]public async Task ResumingAnOriginalLandingKeepsItsDeployedGearWithoutWritingAnyInput()
    {
        var channel=new Channel();var player=Player(4) with{CommanderId="F1",CurrentShipId=7,LoadGameAt=DateTimeOffset.UtcNow.AddHours(-1),Navigation=new(){CurrentSystemAddress=1}};
        var retracted=await TravelPreparation.EnsureRetractedAsync(channel,()=>player,default,42,
            _=>Task.FromResult(TravelPreparation.PreserveOriginalLanding(Landing,Arrival,DateTimeOffset.UtcNow)));
        Check.That(!retracted);Check.Equal(0,channel.Reads);Check.Equal(0,channel.Writes.Count);
    }
    [Test]public async Task DockingOrOriginalHandoffDuringAnInputReadWinsBeforeAnyRetractionWrite()
    {
        foreach(var completes in new[]{false,true})
        {
            var player=Player(4) with{CommanderId="F1",CurrentShipId=7,LoadGameAt=DateTimeOffset.UtcNow.AddHours(-1),Navigation=new(){CurrentSystemAddress=1}};
            var landing=false;var channel=new Channel{OnRead=()=>{landing=true;if(completes)player=player with{Docked=true,MarketId=20};}};
            var retracted=await TravelPreparation.EnsureRetractedAsync(channel,()=>player,default,42,
                _=>Task.FromResult(TravelPreparation.AtDockedDestination(player,Arrival)||landing&&TravelPreparation.PreserveOriginalLanding(Landing,Arrival,DateTimeOffset.UtcNow)));
            Check.That(!retracted);Check.Equal(1,channel.Reads);Check.Equal(0,channel.Writes.Count);
        }
    }
    [Test]public async Task ArrivalYieldCannotHideCancellationOrChangedShipHealthIdentity()
    {
        foreach(var cause in new[]{"cancel","commander","ship","session","hull","threat"})
        {
            using var stop=new CancellationTokenSource();var channel=new Channel();var player=Player(4) with{CommanderId="F1",CurrentShipId=7};
            try
            {
                await TravelPreparation.EnsureRetractedAsync(channel,()=>player,stop.Token,42,async _=>
                {
                    await Task.Delay(20);
                    if(cause=="cancel")stop.Cancel();
                    else if(cause=="commander")player=player with{CommanderId="other"};
                    else if(cause=="ship")player=player with{CurrentShipId=8};
                    else if(cause=="session")player=player with{LoadGameAt=DateTimeOffset.UtcNow};
                    else if(cause=="hull")player=player with{Ships=[new(){ShipId=7,HullHealth=0}]};
                    else player=player with{Navigation=new(){ExternalThreat=true}};
                    return true;
                });
                throw new Exception("An arrival must not override protection or user cancellation.");
            }
            catch(OperationCanceledException)when(cause=="cancel"){}
            catch(InvalidOperationException)when(cause!="cancel"){}
            Check.Equal(0,channel.Writes.Count);
        }
    }
    [Test]public void OriginalLandingYieldRequiresFreshSameTargetClearanceAndNoManualOverride()
    {
        var frame=Landing;var now=DateTimeOffset.UtcNow;Check.That(TravelPreparation.PreserveOriginalLanding(frame,Arrival,now));
        foreach(var invalid in new[]{frame with{PlayerOverride=true},frame with{ExternalThreat=true},frame with{DockingAssist=NativeAssistState.Ready},
            frame with{ObservedAt=now.AddSeconds(-1)},frame with{SystemAddress=2},frame with{Clearance=frame.Clearance! with{MarketId=99}},
            frame with{Clearance=frame.Clearance! with{ExpiresAt=now.AddMilliseconds(-1)}}})
            Check.That(!TravelPreparation.PreserveOriginalLanding(invalid,Arrival,now));
        Check.That(!TravelPreparation.AtDockedDestination(Player(4) with{Docked=true,MarketId=99,Navigation=new(){CurrentSystemAddress=1}},Arrival));
    }
}