using System.Globalization; using System.IO.Pipes; using System.Text.Json; using XFEEDToolBox.Core.Galaxy; using XFEExtension.NetCore.XUnit.Attributes; namespace XFEEDToolBox.Test; [TestFixture] public sealed class NativeGalaxyCarrierWireTests { private const long First=9007199254740993; private static object Carrier(bool dockable)=>new{dockable}; private static object Row(long address,params (string Name,object? Value)[] fields) { var row=new Dictionary{{"systemAddress",address.ToString(CultureInfo.InvariantCulture)}}; foreach(var field in fields)row.Add(field.Name,field.Value); return row; } private static object Page(string origin,int total,bool done,params object[] rows)=>new {generation=19UL,contextSystemAddress=origin,total,done,error="",items=rows}; private static async Task Serve(int pid,object result,CancellationToken token,Action? inspect=null) { await using var server=new NamedPipeServerStream($"XFEEDToolBox.Navigation.{pid}",PipeDirection.InOut,1,PipeTransmissionMode.Byte,PipeOptions.Asynchronous); await server.WaitForConnectionAsync(token);var buffer=new byte[8192];var count=0; while(true) { if(count==buffer.Length)throw new InvalidDataException("Fixture request exceeds its bound."); var read=await server.ReadAsync(buffer.AsMemory(count),token);if(read==0)throw new EndOfStreamException();count+=read; JsonDocument request; try{request=JsonDocument.Parse(buffer.AsMemory(0,count));}catch(JsonException){continue;} using(request)inspect?.Invoke(request.RootElement); break; } await server.WriteAsync(JsonSerializer.SerializeToUtf8Bytes(new{ok=true,result}),token); await server.WriteAsync(new byte[]{10},token);await server.FlushAsync(token); if(OperatingSystem.IsWindows())server.WaitForPipeDrain(); } private static void Inspect(JsonElement request,bool carrier,bool civilization=false,bool services=false) { Check.Equal("galaxy.facts",request.GetProperty("op").GetString()); Check.Equal(carrier,request.GetProperty("includeCarrier").GetBoolean()); Check.Equal(civilization,request.GetProperty("includeCivilization").GetBoolean()); Check.Equal(services,request.GetProperty("includeServices").GetBoolean()); Check.Equal(First.ToString(CultureInfo.InvariantCulture),request.GetProperty("systemAddresses")[0].GetString()); } private static async Task ReadOne(object row,bool carrier,bool civilization=false,bool services=false) { var pid=Random.Shared.Next(200000000,210000000);using var stop=new CancellationTokenSource(TimeSpan.FromSeconds(8)); var server=Task.Run(()=>Serve(pid,Page("42",1,true,row),stop.Token,r=>Inspect(r,carrier,civilization,services))); var result=await new NativeGalaxyFilterFactsCatalog(pid).ReadAsync([First],stop.Token,includeCarrier:carrier,includeCivilization:civilization,includeServices:services); await server;Check.Equal(42L,result.OriginSystemAddress);return result.Rows.Single(); } private static async Task RejectAndRecover(object row,bool requested) { var pid=Random.Shared.Next(200000000,210000000);using var stop=new CancellationTokenSource(TimeSpan.FromSeconds(8)); var server=Task.Run(async()=> { await Serve(pid,Page("42",1,true,row),stop.Token,r=>Inspect(r,requested)); await Serve(pid,new{cancelled=true},stop.Token,r=> {Check.Equal("galaxy.facts.cancel",r.GetProperty("op").GetString());Check.Equal(19UL,r.GetProperty("generation").GetUInt64());}); await Serve(pid,Page("42",1,true,Row(First,("carrier",null))),stop.Token,r=>Inspect(r,requested)); }); var catalog=new NativeGalaxyFilterFactsCatalog(pid);var rejected=false; try{await catalog.ReadAsync([First],stop.Token,includeCarrier:requested);}catch(InvalidDataException){rejected=true;} Check.That(rejected,"Invalid carrier receipt was accepted."); var recovered=await catalog.ReadAsync([First],stop.Token,includeCarrier:requested);await server; Check.That(recovered.Rows.Single().CarrierDockable is null);Check.Equal(requested,recovered.Rows.Single().CarrierRequested); } [Test] public async Task RequestedCarrierPreservesTrueFalseAndExplicitUnknownForExactAddresses() { var pid=Random.Shared.Next(200000000,210000000);using var stop=new CancellationTokenSource(TimeSpan.FromSeconds(8)); var server=Task.Run(()=>Serve(pid,Page("42",3,true, Row(First,("carrier",Carrier(true))),Row(First+1,("carrier",Carrier(false))),Row(First+2,("carrier",null))),stop.Token,r=>Inspect(r,true))); var result=await new NativeGalaxyFilterFactsCatalog(pid).ReadAsync([First,First+1,First+2],stop.Token,includeCarrier:true);await server; Check.That(result.Rows.All(row=>row.CarrierRequested&&!row.CivilizationRequested&&!row.ServicesRequested)); Check.Equal(First,result.Rows[0].SystemAddress);Check.Equal(First+1,result.Rows[1].SystemAddress);Check.Equal(First+2,result.Rows[2].SystemAddress); Check.That(result.Rows[0].CarrierDockable==true&&result.Rows[1].CarrierDockable==false&&result.Rows[2].CarrierDockable is null); } [Test] public async Task UnrequestedMissingAndExplicitNullDoNotReuseAnEarlierClientResult() { var pid=Random.Shared.Next(200000000,210000000);using var stop=new CancellationTokenSource(TimeSpan.FromSeconds(8)); var server=Task.Run(async()=> { await Serve(pid,Page("42",1,true,Row(First,("carrier",Carrier(true)))),stop.Token,r=>Inspect(r,true)); await Serve(pid,Page("42",1,true,Row(First)),stop.Token,r=>Inspect(r,false)); await Serve(pid,Page("42",1,true,Row(First,("carrier",null))),stop.Token,r=>Inspect(r,false)); }); var catalog=new NativeGalaxyFilterFactsCatalog(pid); Check.That((await catalog.ReadAsync([First],stop.Token,includeCarrier:true)).Rows.Single().CarrierDockable==true); for(var i=0;i<2;i++)Check.That((await catalog.ReadAsync([First],stop.Token)).Rows.Single() is{CarrierRequested:false,CarrierDockable:null}); await server; } [Test] public async Task RequestedMissingBindingAndMalformedDockableAreRejectedWithQueueCleanup() { await RejectAndRecover(Row(First),true); foreach(var invalid in new[]{"true","false","1","\"false\"","[]","{}","{\"dockable\":null}","{\"dockable\":1}", "{\"dockable\":\"false\"}","{\"dockable\":[]}","{\"dockable\":{}}"}) await RejectAndRecover(Row(First,("carrier",Check.Json(invalid))),true); } [Test] public async Task AnyUnrequestedNonNullCarrierPayloadIsRejectedEvenWhenItLooksFalse() { foreach(var value in new[]{"{\"dockable\":true}","{\"dockable\":false}","{\"dockable\":null}","false","{}"}) await RejectAndRecover(Row(First,("carrier",Check.Json(value))),false); } [Test] public async Task CarrierIsIndependentOfCivilizationAndServicesForEveryAddress() { var pid=Random.Shared.Next(200000000,210000000);using var stop=new CancellationTokenSource(TimeSpan.FromSeconds(8)); var server=Task.Run(()=>Serve(pid,Page("42",3,true, Row(First,("carrier",null),("civilization",new{definitionId="9007199254740995",key="guardian",defaulted=false}),("services",new{mask=513,defaulted=false})), Row(First+1,("carrier",Carrier(false)),("civilization",null),("services",null)), Row(First+2,("carrier",Carrier(true)),("civilization",new{definitionId="18446744073709551615",key="none",defaulted=true}),("services",new{mask=0,defaulted=true}))),stop.Token,r=>Inspect(r,true,true,true))); var result=await new NativeGalaxyFilterFactsCatalog(pid).ReadAsync([First,First+1,First+2],stop.Token,includeCarrier:true,includeCivilization:true,includeServices:true);await server; Check.That(result.Rows.All(row=>row.CarrierRequested&&row.CivilizationRequested&&row.ServicesRequested)); Check.That(result.Rows[0] is{CarrierDockable:null,Civilization.Key:"guardian",ServiceMask:513,ServicesDefaulted:false}); Check.That(result.Rows[1] is{CarrierDockable:false,Civilization:null,ServiceMask:null,ServicesDefaulted:null}); Check.That(result.Rows[2] is{CarrierDockable:true,Civilization.Key:"none",Civilization.Defaulted:true,ServiceMask:0,ServicesDefaulted:true}); } [Test] public async Task ProgressivePagesDoNotCopyOneAddressesCarrierValueIntoAnother() { var pid=Random.Shared.Next(200000000,210000000);using var stop=new CancellationTokenSource(TimeSpan.FromSeconds(8)); var server=Task.Run(async()=> { await Serve(pid,Page("42",2,false,Row(First,("carrier",Carrier(false)))),stop.Token,r=>Inspect(r,true)); await Serve(pid,Page("42",2,true,Row(First+1,("carrier",null))),stop.Token,r=> {Check.Equal("galaxy.facts.result",r.GetProperty("op").GetString());Check.Equal(1,r.GetProperty("offset").GetInt32());Check.Equal(19UL,r.GetProperty("generation").GetUInt64());}); }); var result=await new NativeGalaxyFilterFactsCatalog(pid).ReadAsync([First,First+1],stop.Token,includeCarrier:true);await server; Check.That(result.Rows[0].CarrierDockable==false&&result.Rows[1].CarrierDockable is null&&result.Rows.All(row=>row.CarrierRequested)); } [Test] public async Task ChangedOriginRejectsMixedPagesAndFreshQueryDoesNotRetainOldDockability() { var pid=Random.Shared.Next(200000000,210000000);using var stop=new CancellationTokenSource(TimeSpan.FromSeconds(8)); var server=Task.Run(async()=> { await Serve(pid,Page("42",2,false,Row(First,("carrier",Carrier(true)))),stop.Token); await Serve(pid,Page("43",2,true,Row(First+1,("carrier",Carrier(false)))),stop.Token); await Serve(pid,new{cancelled=true},stop.Token,r=>Check.Equal("galaxy.facts.cancel",r.GetProperty("op").GetString())); await Serve(pid,Page("43",1,true,Row(First,("carrier",null))),stop.Token,r=>Inspect(r,true)); }); var catalog=new NativeGalaxyFilterFactsCatalog(pid);var rejected=false; try{await catalog.ReadAsync([First,First+1],stop.Token,includeCarrier:true);}catch(InvalidDataException){rejected=true;} Check.That(rejected);var result=await catalog.ReadAsync([First],stop.Token,includeCarrier:true);await server; Check.Equal(43L,result.OriginSystemAddress);Check.That(result.Rows.Single() is{CarrierRequested:true,CarrierDockable:null}); } private static GalaxySystemData Data(GalaxyNativeFilterFact row,string account="F1",int pid=42,long origin=100)=>new() {NativeFilterFacts=new(account,pid,origin){CarrierDockable=row.CarrierDockable,CarrierRequested=row.CarrierRequested, Civilization=row.Civilization,CivilizationRequested=row.CivilizationRequested,ServiceMask=row.ServiceMask,ServicesRequested=row.ServicesRequested,ServicesDefaulted=row.ServicesDefaulted}}; [Test] public async Task UnrequestedWireUpdatesPreserveBothKnownCarrierValuesButExplicitNullClearsThem() { foreach(var dockable in new[]{false,true}) { var initial=Data(await ReadOne(Row(First,("carrier",Carrier(dockable)),("civilization",new{definitionId="9007199254740995",key="guardian",defaulted=false}),("services",new{mask=513,defaulted=false})),true,true,true)); var civilizationOnly=Data(await ReadOne(Row(First,("civilization",new{definitionId="9007199254740997",key="human",defaulted=false})),false,true)); var state=initial.Merge(civilizationOnly); Check.That(state.NativeFilterFacts is{CarrierRequested:true,Civilization.Key:"human",ServicesRequested:true,ServiceMask:513}); Check.Equal((bool?)dockable,state.NativeFilterFacts!.CarrierDockable); var serviceOnly=Data(await ReadOne(Row(First,("services",new{mask=0,defaulted=true})),false,false,true)); state=state.Merge(serviceOnly);Check.Equal((bool?)dockable,state.NativeFilterFacts!.CarrierDockable); Check.That(state.NativeFilterFacts is{CarrierRequested:true,Civilization.Key:"human",ServiceMask:0,ServicesDefaulted:true}); var unknown=Data(await ReadOne(Row(First,("carrier",null)),true));state=state.Merge(unknown); Check.That(state.NativeFilterFacts is{CarrierRequested:true,CarrierDockable:null,Civilization.Key:"human",ServiceMask:0,ServicesDefaulted:true}); } } [Test] public async Task AccountProcessAndOriginChangesNeverInheritUnrequestedCarrierEvidence() { var initial=Data(await ReadOne(Row(First,("carrier",Carrier(false))),true)); var unrelated=await ReadOne(Row(First),false); foreach(var context in new[]{(Account:"F2",Pid:42,Origin:100L),(Account:"F1",Pid:43,Origin:100L),(Account:"F1",Pid:42,Origin:101L)}) { Check.That(initial.ForNativeContext(context.Account,context.Pid,context.Origin).NativeFilterFacts is null); var changed=initial.Merge(Data(unrelated,context.Account,context.Pid,context.Origin)); Check.That(changed.NativeFilterFacts is{CarrierRequested:false,CarrierDockable:null}); } } }