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 GameRoutePlannerTests
{
    private const long Destination=9007199254740995;
    private static readonly NavWaypoint Target=new(Destination,"Destination",new(30,0,0),"K");
    private static PlayerState Initial()=>new(){InGame=true,SystemName="Origin",Position=new(0,0,0),Navigation=new(){CurrentSystemAddress=1}};
    private sealed class Driver:IGameRoutePlanningDriver
    {
        public bool SupportsGameRoutePlanning { get; set; }=true;
        public int Requests,Reads;
        public NavWaypoint? Submitted;
        public Func<int,GameRouteStatus>? Read;
        public Task<long> RequestGameRouteAsync(NavWaypoint target,CancellationToken token)
        {Requests++;Submitted=target;return Task.FromResult(7L);}
        public Task<GameRouteStatus> ReadGameRouteStatusAsync(long generation,long destinationAddress,CancellationToken token)
        {token.ThrowIfCancellationRequested();return Task.FromResult(Read?.Invoke(++Reads)??Status());}
    }
    private static GameRouteStatus Status(long selected=Destination,bool fresh=true,IReadOnlyList<NavWaypoint>? route=null)=>new(7,true,false,false,Destination,selected,fresh,true,(route??DefaultRoute()).Count,GameRouteFingerprint.Compute(route??DefaultRoute()));
    private static IReadOnlyList<NavWaypoint> DefaultRoute()=>[new(1,"Origin",new(0,0,0),"F"),Target];
    private static async Task Rejects<T>(Func<Task> operation) where T:Exception
    {try{await operation();}catch(T){return;}throw new InvalidOperationException("Expected "+typeof(T).Name);}

    [Test] public async Task CachedMatchingRouteCannotConfirmNewRequestAndNextTargetMustMatch()
    {
        var old=new[]{new NavWaypoint(1,"Origin",new(0,0,0),"F"),Target};
        var state=Initial() with{Navigation=Initial().Navigation with{Route=old}};
        var driver=new Driver();
        driver.Read=count=>
        {
            if(count==2)state=state with{Navigation=state.Navigation with{Route=old.ToArray()}};
            return Status(count<3?42:Destination) with{TopologyFresh=count>1};
        };
        var result=await new GameRoutePlanner(()=>state,TimeSpan.FromSeconds(3)).PlanAsync(Target,driver);
        Check.Equal(1,driver.Requests);Check.Equal(3,driver.Reads);Check.Equal(Destination,driver.Submitted!.SystemAddress);
        Check.That(!ReferenceEquals(old,result.Route));Check.Equal(Destination,result.Next.SystemAddress);
    }

    [Test] public async Task SeveralThousandGameWaypointsArePreservedWithoutLocalRouteSelection()
    {
        var state=Initial();var gameRoute=Enumerable.Range(0,5000).Select(i=>new NavWaypoint(i+1,"Game "+i,new(i,i%7,-i%11),i%2==0?"K":"L")).Append(Target).ToArray();
        var driver=new Driver();driver.Read=count=>{state=state with{Navigation=state.Navigation with{Route=gameRoute}};return Status(gameRoute[1].SystemAddress,route:gameRoute);};
        var result=await new GameRoutePlanner(()=>state).PlanAsync(Target,driver);
        Check.That(ReferenceEquals(gameRoute,result.Route));Check.Equal(5001,result.Route.Count);Check.Equal(2L,result.Next.SystemAddress);
        Check.Equal("Game 3789",result.Route[3789].Name);Check.Equal(1,driver.Requests);
    }

    [Test] public async Task NoGameRouteCannotBecomeDirectJumpEvenWhenNativeDestinationMatches()
    {
        var state=Initial() with{MaxJumpRange=1000};var driver=new Driver();
        await Rejects<TimeoutException>(async()=>{await new GameRoutePlanner(()=>state,TimeSpan.FromMilliseconds(250)).PlanAsync(Target,driver);});
        Check.Equal(0,state.Navigation.Route.Count);Check.Equal(1,driver.Requests);
    }
    [Test] public async Task UnchangedJournalCacheRequiresTheEntireLiveGamePathToMatch()
    {
        var route=DefaultRoute();var state=Initial() with{Navigation=Initial().Navigation with{Route=route}};
        var driver=new Driver{Read=count=>count==1?Status() with{RouteFingerprint=123}:Status()};
        var result=await new GameRoutePlanner(()=>state).PlanAsync(Target,driver);
        Check.Equal(2,driver.Reads);Check.That(ReferenceEquals(route,result.Route));Check.Equal(GameRouteFingerprint.Compute(route),result.NativeRouteFingerprint);
    }

    [Test] public async Task ChangedDestinationOrOriginCannotConfirmSuccessfulPlan()
    {
        var state=Initial();var driver=new Driver{Read=_=>Status() with{DestinationAddress=42}};
        await Rejects<InvalidOperationException>(async()=>{await new GameRoutePlanner(()=>state).PlanAsync(Target,driver);});
        driver=new Driver{Read=_=>{state=state with{Navigation=state.Navigation with{CurrentSystemAddress=42}};return Status();}};
        await Rejects<InvalidOperationException>(async()=>{await new GameRoutePlanner(()=>state).PlanAsync(Target,driver);});
    }
    [Test] public async Task MatchingAddressWithAnUnrelatedEndpointNameIsRejected()
    {
        var target=Target with{Name="Adnyayar"};
        var route=new[]{DefaultRoute()[0],target with{Name="Col 285 Sector MC-I b11-2"}};
        var state=Initial() with{Navigation=Initial().Navigation with{Route=route}};
        var driver=new Driver{Read=_=>Status(route:route)};
        await Rejects<InvalidDataException>(()=>new GameRoutePlanner(()=>state).PlanAsync(target,driver));
        Check.Equal("Adnyayar",driver.Submitted!.Name);Check.Equal(1,driver.Reads);
    }

    [Test] public async Task UnsupportedPluginAndRejectedGameRequestCannotReportReady()
    {
        var driver=new Driver{SupportsGameRoutePlanning=false};
        await Rejects<InvalidOperationException>(async()=>{await new GameRoutePlanner(Initial).PlanAsync(Target,driver);});
        Check.Equal(0,driver.Requests);
        driver=new Driver{Read=_=>new(7,false,false,true,0,0,false)};
        await Rejects<InvalidOperationException>(async()=>{await new GameRoutePlanner(Initial).PlanAsync(Target,driver);});
    }
    [Test] public async Task TemporaryRouteStatusDisconnectKeepsTheSameGameRequestUntilConfirmation()
    {
        var state=Initial() with{Navigation=Initial().Navigation with{Route=DefaultRoute()}};
        var driver=new Driver{Read=i=>i==1?throw new IOException("Brief pipe disconnect"):Status()};var recovering=false;
        var result=await new GameRoutePlanner(()=>state,TimeSpan.FromSeconds(3)).PlanAsync(Target,driver,s=>recovering|=s.Contains("重新读取"));
        Check.That(recovering);Check.Equal(1,driver.Requests);Check.Equal(2,driver.Reads);Check.Equal(Destination,result.Next.SystemAddress);
    }
}