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<string,object?>{{"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<JsonElement>? 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<GalaxyNativeFilterFact> 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});
}
}
}
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<string,object?>{{"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<JsonElement>? 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<GalaxyNativeFilterFact> 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});
}
}
}