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

XFEEDToolBox

【WinUI】XFE精英危险工具箱

公开
关注 0 Fork 0 Star 0
UTF-8
using System.IO.Pipes;
using System.Text.Json;
using XFEEDToolBox.Core.Galaxy;
using XFEExtension.NetCore.XUnit.Attributes;

namespace XFEEDToolBox.Test;

[TestFixture]
public sealed class NativeGalaxyCurrentTradeFlowsTests
{
    private const long First=9007199254740993,Second=9007199254740997;
    private static async Task Serve(int pid,object result,CancellationToken token,Action<JsonElement>? inspect=null)
    {
        await using var server=new NamedPipeServerStream($"XFEEDToolBox.Navigation.{pid}",PipeDirection.InOut,1,PipeTransmissionMode.Byte,PipeOptions.Asynchronous);
        await server.WaitForConnectionAsync(token);var bytes=new byte[8192];var count=await server.ReadAsync(bytes,token);
        using(var request=JsonDocument.Parse(bytes.AsMemory(0,count)))inspect?.Invoke(request.RootElement);
        await server.WriteAsync(JsonSerializer.SerializeToUtf8Bytes(new{ok=true,result}),token);await server.WriteAsync(new byte[]{10},token);
        if(OperatingSystem.IsWindows())server.WaitForPipeDrain();
    }
    private static Dictionary<string,object?> Row(long from=First,long to=Second,ushort token=62000,string? name="原商品·茶",ulong timestamp=1000,float npc=.25f,float player=.5f)=>new()
    {
        ["fromSystemAddress"]=from.ToString(),["toSystemAddress"]=to.ToString(),["commodityToken"]=token,["commodityName"]=name,
        ["timestamp"]=timestamp.ToString(),["npcAmount"]=npc,["playerAmount"]=player
    };
    private static Dictionary<string,object?> Page(int total,int offset,int? next,ulong clock=1000,int unresolved=0,params object[] items)=>new()
    {
        ["available"]=true,["generation"]=17UL,["entityId"]="9007199254740999",["source"]=GalaxyCurrentTradeFlowsSnapshot.Source,["complete"]=false,
        ["originalClock"]=clock.ToString(),["lifetimeSeconds"]=100UL,["refreshSeconds"]=50UL,["revision"]=7U,["unresolvedCount"]=unresolved,
        ["total"]=total,["offset"]=offset,["nextOffset"]=next,["items"]=items
    };
    [Test] public async Task ExactAddressesUtf8ZeroAmountsAndMonotonicOriginalClockSurvivePagination()
    {
        var pid=Random.Shared.Next(180000000,190000000);using var stop=new CancellationTokenSource(TimeSpan.FromSeconds(5));
        var server=Task.Run(async()=>
        {
            await Serve(pid,Page(2,0,1,1000,1,Row(timestamp:901,npc:0,player:0)),stop.Token,r=>
            {Check.Equal("galaxy.tradeflows",r.GetProperty("op").GetString());Check.Equal(16,r.GetProperty("limit").GetInt32());});
            await Serve(pid,Page(2,1,null,1001,1,Row(Second,First,name:null,timestamp:1001)),stop.Token,r=>
            {Check.Equal(17UL,r.GetProperty("generation").GetUInt64());Check.Equal(1,r.GetProperty("offset").GetInt32());});
        });
        var result=await new NativeGalaxyAccountCatalog(pid).ReadCurrentTradeFlowsAsync(stop.Token);await server;
        Check.That(result.Available&&!result.Complete);Check.Equal(1001UL,result.OriginalClock);Check.Equal(7U,result.Revision);
        Check.Equal(First,result.Flows[0].FromSystemAddress);Check.Equal(Second,result.Flows[0].ToSystemAddress);Check.Equal((ushort)62000,result.Flows[0].CommodityToken);
        Check.Equal("原商品·茶",result.Flows[0].CommodityName);Check.Equal(0f,result.Flows[0].NpcAmount);Check.Equal(0f,result.Flows[0].PlayerAmount);
        Check.That(result.Flows[1].CommodityName is null);Check.Equal(1,result.UnresolvedCount);
    }
    [Test] public async Task SourceRevisionEntityClockAndPaginationChangesCannotMixSnapshots()
    {
        foreach(var change in new Action<Dictionary<string,object?>>[]
        {
            page=>page["source"]="historical-trd",page=>page["revision"]=8U,page=>page["entityId"]="other",
            page=>page["originalClock"]="999",page=>page["lifetimeSeconds"]=101UL,page=>page["generation"]=18UL,
            page=>page["offset"]=0,page=>page["nextOffset"]=2,page=>page["complete"]=true
        })
        {
            var pid=Random.Shared.Next(180000000,190000000);using var stop=new CancellationTokenSource(TimeSpan.FromSeconds(5));
            var last=Page(2,1,null,1000,0,Row(Second,First));change(last);
            var server=Task.Run(async()=>{await Serve(pid,Page(2,0,1,1000,0,Row()),stop.Token);await Serve(pid,last,stop.Token);});
            var rejected=false;try{await new NativeGalaxyAccountCatalog(pid).ReadCurrentTradeFlowsAsync(stop.Token);}catch(InvalidDataException){rejected=true;}
            await server;Check.That(rejected);
        }
    }
    [Test] public async Task FutureExpiredDuplicateUnknownNameCountAndInvalidRowsAreRejected()
    {
        var duplicate=Row();
        var badName=Row(name:"bad\nname");var badNpc=Row(npc:1.1f);var sameEndpoint=Row(to:First);
        foreach(var page in new[]
        {
            Page(1,0,null,1000,0,Row(timestamp:1001)),Page(1,0,null,1000,0,Row(timestamp:899)),
            Page(2,0,null,1000,0,duplicate,duplicate),Page(1,0,null,1000,0,Row(name:null)),
            Page(1,0,null,1000,0,badName),Page(1,0,null,1000,0,badNpc),Page(1,0,null,1000,0,sameEndpoint)
        })
        {
            var pid=Random.Shared.Next(180000000,190000000);using var stop=new CancellationTokenSource(TimeSpan.FromSeconds(5));
            var server=Task.Run(()=>Serve(pid,page,stop.Token));var rejected=false;
            try{await new NativeGalaxyAccountCatalog(pid).ReadCurrentTradeFlowsAsync(stop.Token);}catch(InvalidDataException){rejected=true;}
            await server;Check.That(rejected);
        }
    }
    [Test] public async Task FinalClockCanExpireAnEarlierPageAndUnderflowNeverBecomesZeroCutoff()
    {
        var pid=Random.Shared.Next(180000000,190000000);using var stop=new CancellationTokenSource(TimeSpan.FromSeconds(5));
        var server=Task.Run(async()=>
        {
            await Serve(pid,Page(2,0,1,1000,0,Row(timestamp:900)),stop.Token);
            await Serve(pid,Page(2,1,null,1001,0,Row(Second,First,timestamp:1001)),stop.Token);
        });
        var rejected=false;try{await new NativeGalaxyAccountCatalog(pid).ReadCurrentTradeFlowsAsync(stop.Token);}catch(InvalidDataException){rejected=true;}
        await server;Check.That(rejected);

        var underflow=Page(0,0,null,50);var secondPid=Random.Shared.Next(180000000,190000000);
        var secondServer=Task.Run(()=>Serve(secondPid,underflow,stop.Token));
        var result=await new NativeGalaxyAccountCatalog(secondPid).ReadCurrentTradeFlowsAsync(stop.Token);await secondServer;
        Check.That(result.Available&&!result.Complete&&result.Flows.Count==0);Check.Equal(50UL,result.OriginalClock);
    }
}