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

XFEEDToolBox

【WinUI】XFE精英危险工具箱

公开
关注 0 Fork 0 Star 0
UTF-8
using System.Text.Json;
using System.Text.Json.Nodes;
using XFEEDToolBox.Core.Models;
using XFEEDToolBox.Core.Session;
using XFEExtension.NetCore.XUnit.Attributes;
namespace XFEEDToolBox.Test;

[TestFixture]
public class TradePauseRecoveryTests
{
    [Test]public void NativeRenewUsesTheUnsignedNumericEntityWireContractWithoutPrecisionLoss()
    {
        foreach(var entity in new[]{11825915469828UL,9007199254740993UL,ulong.MaxValue})
        {
            var request=JsonSerializer.SerializeToElement(new NativeTradePauseRenewRequest(new string('a',32),new string('b',32),entity),ToolboxSettings.JsonOptions);
            // This is the exact decoder used by native JsonWire.U64. A JSON
            // string would throw here even when it contains decimal digits.
            Check.Equal(JsonValueKind.Number,request.GetProperty("entityId").ValueKind);
            Check.Equal(entity,request.GetProperty("entityId").GetUInt64());
            Check.Equal("trade.pause.renew",request.GetProperty("op").GetString());
            Check.Equal(750,request.GetProperty("ttlMilliseconds").GetInt32());
        }
        var oldWire=JsonSerializer.SerializeToElement(new{entityId="11825915469828"});
        Check.Throws<InvalidOperationException>(()=>oldWire.GetProperty("entityId").GetUInt64());
    }
    private static readonly DateTimeOffset Now=DateTimeOffset.Parse("2026-10-25T12:00:00Z");
    private static PlayerState Player=>new(){InGame=true,CommanderId="owner",CurrentShipId=5,LoadGameAt=Now.AddHours(-1)};
    private static JsonElement Menu(Action<JsonObject>? change=null)
    {
        var value=JsonSerializer.SerializeToNode(new{state="Pause",nextState="Pause",menuReady=true,kind=1,categoryName="Pause",menuSequence=12UL,
            observedAtUnixMilliseconds=Now.ToUnixTimeMilliseconds(),flightTelemetryFresh=false,
            actions=new[]{new{name="InGame",enabled=true},new{name="ExitOptions",enabled=true}},
            tradePauseIdentity=new{verified=true,entityId="42",hullFraction=1d,observedAtUnixMilliseconds=Now.ToUnixTimeMilliseconds(),physicsTelemetryRequired=false},
            tradePauseRecovery=new{requestId="",applied=false,failed=false}})!.AsObject();
        change?.Invoke(value);return JsonSerializer.SerializeToElement(value);
    }
    private sealed class Channel:ITradePauseRecoveryChannel
    {
        public JsonElement Value=Menu();public List<string> Calls=[];public Action? AfterRead,AfterRenew;public string? ResumeId;
        public Task PrepareAsync(CancellationToken token){Calls.Add("prepare");return Task.CompletedTask;}
        public Task<JsonElement> ReadAsync(CancellationToken token){Calls.Add("read");AfterRead?.Invoke();return Task.FromResult(Value);}
        public Task RenewAsync(string owner,string run,ulong entity,CancellationToken token){Calls.Add("renew");Check.Equal(42UL,entity);AfterRenew?.Invoke();return Task.CompletedTask;}
        public Task ResumeAsync(string owner,string run,string id,ulong sequence,CancellationToken token){Calls.Add("resume");ResumeId=id;Check.Equal(12UL,sequence);return Task.CompletedTask;}
        public Task EndAsync(string owner,string run){Calls.Add("end");return Task.CompletedTask;}
    }
    [Test]public async Task OnlyARunningScopeCanSubmitThePreciseOriginalPauseResume()
    {
        var running=false;var channel=new Channel();var session=new TradePauseRecoverySession(channel,()=>Player,()=>running,()=>Now);
        await session.StepAsync(default);Check.Equal(0,channel.Calls.Count);
        running=true;await session.StepAsync(default);Check.Equal(1,channel.Calls.Count(x=>x=="resume"));
        running=false;await session.StepAsync(default);Check.Equal("end",channel.Calls.Last());
        await session.EndAsync();running=true;await session.StepAsync(default);Check.Equal(1,channel.Calls.Count(x=>x=="resume"));
    }
    [Test]public async Task StaleFlightTelemetryDoesNotBlockFreshOriginalMenuThreadIdentity()
    {
        var channel=new Channel();var session=new TradePauseRecoverySession(channel,()=>Player,()=>true,()=>Now);
        await session.StepAsync(default);Check.That(channel.Calls.Contains("resume"));
        var stale=new Channel{Value=Menu(row=>row["tradePauseIdentity"]!["observedAtUnixMilliseconds"]=Now.AddSeconds(-1).ToUnixTimeMilliseconds())};
        await new TradePauseRecoverySession(stale,()=>Player,()=>true,()=>Now).StepAsync(default);Check.That(!stale.Calls.Contains("renew")&&!stale.Calls.Contains("resume"));
    }
    [Test]public async Task ErrorsRespawnExitAndOrdinaryPanelsCannotBeDismissed()
    {
        foreach(var state in new[]{"InGame","MainMenu","ExitOptions","Rebirth","NetworkError","Pause Options","Supercruise"})
        {
            var channel=new Channel{Value=Menu(row=>{row["state"]=state;row["nextState"]=state;})};
            await new TradePauseRecoverySession(channel,()=>Player,()=>true,()=>Now).StepAsync(default);Check.That(!channel.Calls.Contains("resume"));
        }
    }
    [Test]public async Task DisabledDuplicateStaleAndWrongCategoryActionsCannotResume()
    {
        foreach(var mode in new[]{0,1,2,3,4})
        {
            var channel=new Channel{Value=Menu(row=>
            {
                if(mode==0)row["actions"]![0]!["enabled"]=false;
                else if(mode==1)row["actions"]![1]!["name"]="InGame";
                else if(mode==2)row["observedAtUnixMilliseconds"]=Now.AddSeconds(-1).ToUnixTimeMilliseconds();
                else if(mode==3)row["kind"]=2;
                else row["nextState"]="ExitOptions";
            })};
            await new TradePauseRecoverySession(channel,()=>Player,()=>true,()=>Now).StepAsync(default);Check.That(!channel.Calls.Contains("resume"));
        }
    }
    [Test]public async Task CancellationBetweenObservationAndSubmissionRevokesWithoutAResume()
    {
        foreach(var afterRenew in new[]{false,true})
        {
            var running=true;var channel=new Channel();
            if(afterRenew)channel.AfterRenew=()=>running=false;else channel.AfterRead=()=>running=false;
            await new TradePauseRecoverySession(channel,()=>Player,()=>running,()=>Now).StepAsync(default);Check.That(!channel.Calls.Contains("resume"));
            if(afterRenew)Check.That(channel.Calls.Contains("end"));
        }
    }
    [Test]public async Task OneRequestWaitsForTheRealInGameObservationRatherThanRepeating()
    {
        var channel=new Channel();var session=new TradePauseRecoverySession(channel,()=>Player,()=>true,()=>Now);
        await session.StepAsync(default);await session.StepAsync(default);Check.Equal(1,channel.Calls.Count(x=>x=="resume"));
        channel.Value=Menu(row=>{row["state"]="InGame";row["nextState"]="InGame";row["tradePauseRecovery"]!["requestId"]=channel.ResumeId;row["tradePauseRecovery"]!["applied"]=true;});
        await session.StepAsync(default);Check.Equal(1,session.ConfirmedResumes);Check.Equal(1,channel.Calls.Count(x=>x=="resume"));
    }
    [Test]public async Task ShipOrSessionChangeAndCurrentUnhealthyIdentityFailClosed()
    {
        var current=Player;var channel=new Channel{Value=Menu(row=>{row["state"]="InGame";row["nextState"]="InGame";})};
        var session=new TradePauseRecoverySession(channel,()=>current,()=>true,()=>Now);
        await session.StepAsync(default);Check.That(channel.Calls.Contains("renew"));
        current=current with{CurrentShipId=6};channel.Value=Menu();
        await session.StepAsync(default);Check.That(channel.Calls.Contains("end")&&!channel.Calls.Contains("resume"));
        foreach(var mode in new[]{0,1,2})
        {
            var invalid=new Channel{Value=Menu(row=>
            {
                if(mode==0)row["tradePauseIdentity"]!["verified"]=false;
                else if(mode==1)row["tradePauseIdentity"]!["hullFraction"]=0;
                else row["tradePauseIdentity"]!["entityId"]="0";
            })};
            await new TradePauseRecoverySession(invalid,()=>Player,()=>true,()=>Now).StepAsync(default);
            Check.That(!invalid.Calls.Contains("renew")&&!invalid.Calls.Contains("resume"));
        }
    }
}