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 NativeGalaxyStateOwnershipWireTests { private const long First=9007199254740993; private static object State(string key="Pseudo39")=>new{stateId=39,pseudoUnpopulated=true,defaulted=false,key,rawRgba=new[]{2.5f,.2f,.3f,0f}}; private static object Owner(bool own=false)=>new{source=GalaxyNativeColonisationOwnership.Source,localCommanderId="9007199254740995", claimCommanderId=own?"9007199254740995":"9007199254740997",claimStatus=2,own}; private static object Basic()=>new{population="9007199254740995",governmentId=4,primaryEconomyId=4,secondaryEconomyId=10, factionStateId=0,allegianceId="9007199254740999",securityValue=40,defaulted=false}; private static object Row(params (string Name,object? Value)[] fields) {var row=new Dictionary{{"systemAddress",First.ToString(CultureInfo.InvariantCulture)}};foreach(var f in fields)row.Add(f.Name,f.Value);return row;} private static object Page(object row)=>new{generation=37UL,contextSystemAddress="42",total=1,done=true,error="",items=new[]{row}}; private static async Task Serve(int pid,object result,CancellationToken stop,Action inspect) { await using var server=new NamedPipeServerStream($"XFEEDToolBox.Navigation.{pid}",PipeDirection.InOut,1,PipeTransmissionMode.Byte,PipeOptions.Asynchronous); await server.WaitForConnectionAsync(stop);var bytes=new byte[8192];var used=0; while(true) { if(used==bytes.Length)throw new InvalidDataException("Fixture request is oversized."); var read=await server.ReadAsync(bytes.AsMemory(used),stop);if(read==0)throw new EndOfStreamException();used+=read; JsonDocument request;try{request=JsonDocument.Parse(bytes.AsMemory(0,used));}catch(JsonException){continue;} using(request)inspect(request.RootElement);break; } await server.WriteAsync(JsonSerializer.SerializeToUtf8Bytes(new{ok=true,result}),stop);await server.WriteAsync(new byte[]{10},stop);await server.FlushAsync(stop); if(OperatingSystem.IsWindows())server.WaitForPipeDrain(); } private static void Inspect(JsonElement request,bool state,bool owner,bool basic=false,bool colony=false) { Check.Equal("galaxy.facts",request.GetProperty("op").GetString()); Check.Equal(state,request.GetProperty("includeState").GetBoolean());Check.Equal(owner,request.GetProperty("includeColonisationOwnership").GetBoolean()); Check.Equal(basic,request.GetProperty("includeBasicSystem").GetBoolean());Check.Equal(colony,request.GetProperty("includeColonisation").GetBoolean()); Check.Equal(First.ToString(CultureInfo.InvariantCulture),request.GetProperty("systemAddresses")[0].GetString()); } private static async Task Read(object row,bool state=false,bool owner=false,bool basic=false,bool colony=false) { var pid=Random.Shared.Next(230000000,240000000);using var stop=new CancellationTokenSource(TimeSpan.FromSeconds(8)); var server=Task.Run(()=>Serve(pid,Page(row),stop.Token,r=>Inspect(r,state,owner,basic,colony))); var result=await new NativeGalaxyFilterFactsCatalog(pid).ReadAsync([First],stop.Token,includeColonisation:colony,includeBasicSystem:basic,includeState:state,includeColonisationOwnership:owner); await server;Check.Equal(42L,result.OriginSystemAddress);return result.Rows.Single(); } private static async Task Reject(object row,bool state=true,bool owner=true,bool colony=false) { var pid=Random.Shared.Next(230000000,240000000);using var stop=new CancellationTokenSource(TimeSpan.FromSeconds(8)); var server=Task.Run(async()=> { await Serve(pid,Page(row),stop.Token,r=>Inspect(r,state,owner,colony:colony)); await Serve(pid,new{cancelled=true},stop.Token,r=>{Check.Equal("galaxy.facts.cancel",r.GetProperty("op").GetString());Check.Equal(37UL,r.GetProperty("generation").GetUInt64());}); await Serve(pid,Page(Row(("state",null),("colonisationOwnership",null))),stop.Token,r=>Inspect(r,state,owner,colony:colony)); }); var client=new NativeGalaxyFilterFactsCatalog(pid);var rejected=false; try{await client.ReadAsync([First],stop.Token,includeColonisation:colony,includeState:state,includeColonisationOwnership:owner);}catch(InvalidDataException){rejected=true;} Check.That(rejected,"Malformed or unsolicited original classification was accepted."); var recovered=(await client.ReadAsync([First],stop.Token,includeColonisation:colony,includeState:state,includeColonisationOwnership:owner)).Rows.Single();await server; Check.That(recovered.State is null&&recovered.ColonisationOwnership is null);Check.Equal(state,recovered.StateRequested);Check.Equal(owner,recovered.ColonisationOwnershipRequested); } private static GalaxySystemData Data(GalaxyNativeFilterFact f,string account="F1",int pid=42,long origin=42)=>new(){AccountFid=account, NativeFilterFacts=new(account,pid,origin){State=f.State,StateRequested=f.StateRequested,ColonisationOwnership=f.ColonisationOwnership, ColonisationOwnershipRequested=f.ColonisationOwnershipRequested,BasicSystem=f.BasicSystem,BasicSystemRequested=f.BasicSystemRequested, ColonisationHighlight=f.ColonisationHighlight,ColonisationHighlightRequested=f.ColonisationHighlightRequested,ColonisationStatus=f.ColonisationStatus, ColonisationNormalStatus=f.ColonisationNormalStatus,ColonisationNormalStatusRequested=f.ColonisationNormalStatusRequested}}; [Test] public async Task OriginalNormalModeIsIndependentFromEligibilityAndDistinguishesUnavailableFromOldWire() { var known=await Read(Row(("colonisationStatus",0),("colonisationNormalStatus",5)),colony:true); Check.That(known.ColonisationNormalStatusRequested&&known.ColonisationNormalStatus==5&&known.ColonisationStatus==0); var pending=await Read(Row(("colonisationStatus",0),("colonisationNormalStatus",null)),colony:true); Check.That(pending.ColonisationNormalStatusRequested&&pending.ColonisationNormalStatus is null); var old=await Read(Row(("colonisationStatus",0)),colony:true);Check.That(!old.ColonisationNormalStatusRequested); await Reject(Row(("colonisationNormalStatus",5)),false,false); await Reject(Row(("colonisationNormalStatus",12)),false,false,true); await Reject(Row(("colonisationNormalStatus",-1)),false,false,true); var filter=GalaxyMapFilter.Default with{Mode=GalaxyMapMode.Colonisation,Category=GalaxyFilterCategory.Colonisation,SizeCategory=GalaxyFilterCategory.Colonisation}; var system=new GalacticSystem("independent-normal-colony",new(0,0,0)){Data=Data(known)}; Check.Equal(.35f,filter.GetOriginalRgba(system)!.Value.W); Check.Equal(GalaxyOriginalAppearance.DefaultSize*GalaxyOriginalSizeOptions.ColonisationExcludedMultiplier,filter.SelectedOriginalSize(system)!.Value); var missing=system with{Data=Data(pending)};Check.That(filter.GetOriginalRgba(missing) is null&&filter.SelectedOriginalSize(missing) is null); Check.That(system.Data.Merge(Data(pending)).NativeFilterFacts!.ColonisationNormalStatus is null); } [Test] public async Task ColonyHighlightWireRequiresMatchingRequestedEligibilityAndKeepsMissingSourceUnknown() { var highlight=new{source=GalaxyNativeColonisationHighlight.Source,centreSystemAddress="9007199254740995",eligibilityStatus=0,distanceSquared=4f,radiusSquared=4f,rawRgb=new[]{.3f,1f,.2f}}; var known=await Read(Row(("colonisationStatus",0),("colonisationHighlight",highlight)),colony:true); Check.That(known.ColonisationHighlight is{Highlighted:true,CentreSystemAddress:9007199254740995L}&&known.ColonisationHighlightRequested); foreach(var row in new[]{Row(("colonisationStatus",0)),Row(("colonisationStatus",0),("colonisationHighlight",null))}) {var missing=await Read(row,colony:true);Check.That(missing.ColonisationHighlightRequested&&missing.ColonisationHighlight is null);} await Reject(Row(("colonisationStatus",0),("colonisationHighlight",highlight)),false,false); await Reject(Row(("colonisationStatus",1),("colonisationHighlight",highlight)),false,false,true); await Reject(Row(("colonisationHighlight",highlight)),false,false,true); await Reject(Row(("colonisationStatus",0),("colonisationHighlight",new{source="journal",centreSystemAddress="42"})),false,false,true); var filter=GalaxyMapFilter.Default with{Mode=GalaxyMapMode.Colonisation,Category=GalaxyFilterCategory.Colonisation}; var system=new GalacticSystem("wire-colony-style",new(0,0,0)){Data=Data(known)}; Check.Equal(.3f,filter.GetOriginalRgba(system)!.Value.X); } [Test] public async Task ExplicitNullAndUnrequestedMissingRemainSeparateFromKnownNegativeOwnership() { foreach(var request in new[]{(State:true,Owner:false),(State:false,Owner:true),(State:true,Owner:true)}) { var unknown=await Read(Row(("state",null),("colonisationOwnership",null)),request.State,request.Owner); Check.Equal(request.State,unknown.StateRequested);Check.Equal(request.Owner,unknown.ColonisationOwnershipRequested); Check.That(unknown.State is null&&unknown.ColonisationOwnership is null); } foreach(var row in new[]{Row(),Row(("state",null),("colonisationOwnership",null))}) {var unrequested=await Read(row);Check.That(!unrequested.StateRequested&&!unrequested.ColonisationOwnershipRequested&&unrequested.State is null&&unrequested.ColonisationOwnership is null);} var known=await Read(Row(("state",State()),("colonisationOwnership",Owner()),("basicSystem",Basic())),true,true,true); Check.That(known.ColonisationOwnership is{Own:false,LocalCommanderId:9007199254740995UL,ClaimCommanderId:9007199254740997UL}); Check.That(known.State is{StateId:39,PseudoUnpopulated:true,Defaulted:false,Key:"Pseudo39"});Check.Equal(2.5f,known.State!.RawRgba![0]);Check.Equal(0f,known.State.RawRgba[3]); Check.Equal(0U,known.BasicSystem!.FactionStateId);Check.Equal(9007199254740995UL,known.BasicSystem.Population); } [Test] public async Task MissingRequestedFieldsAndUnrequestedNonNullAreRejectedAndOriginalGenerationIsReleased() { await Reject(Row(("state",State())));await Reject(Row(("colonisationOwnership",Owner()))); await Reject(Row(("state",State()),("colonisationOwnership",null)),false,true); await Reject(Row(("state",null),("colonisationOwnership",Owner())),true,false); await Reject(Row(("state",State())),false,false);await Reject(Row(("colonisationOwnership",Owner())),false,false); } [Test] public async Task InvalidStateAndOwnershipDtosCannotTurnIntoDefaultOrForeignFacts() { foreach(var invalid in new[]{"{}","false","{\"stateId\":3,\"pseudoUnpopulated\":true,\"defaulted\":false,\"key\":\"War\",\"rawRgba\":[1,1,1,1]}", "{\"stateId\":39,\"pseudoUnpopulated\":true,\"defaulted\":true,\"key\":\"War\",\"rawRgba\":[1,1,1,1]}", "{\"stateId\":0,\"pseudoUnpopulated\":false,\"defaulted\":false,\"key\":null,\"rawRgba\":[1,1,1,1]}", "{\"stateId\":0,\"pseudoUnpopulated\":false,\"defaulted\":false,\"key\":\"None\",\"rawRgba\":[-1,1,1,1]}"}) await Reject(Row(("state",Check.Json(invalid)),("colonisationOwnership",null))); var valid=JsonSerializer.Serialize(Owner()); foreach(var invalid in new[]{"{}","[]",valid.Replace("9007199254740995","0"),valid.Replace("original-colonisation-classifier-owner","public-colony"), valid.Replace("\"claimStatus\":2","\"claimStatus\":4"),valid.Replace("\"own\":false","\"own\":true"),valid.Replace("\"9007199254740995\"","9007199254740995")}) await Reject(Row(("state",null),("colonisationOwnership",Check.Json(invalid)))); } [Test] public async Task IndependentStateOwnershipAndBasicRequestsPreserveUnrequestedAndClearOnlyExplicitUnknown() { var initial=Data(await Read(Row(("state",State()),("colonisationOwnership",Owner()),("basicSystem",Basic())),true,true,true)); var stateOnly=await Read(Row(("state",State("ActualState"))),state:true);Check.That(!stateOnly.ColonisationOwnershipRequested&&!stateOnly.BasicSystemRequested); var merged=initial.Merge(Data(stateOnly));Check.That(merged.NativeFilterFacts is{State.Key:"ActualState",ColonisationOwnership.Own:false,BasicSystem.Population:9007199254740995UL}); merged=merged.Merge(Data(await Read(Row(("colonisationOwnership",Owner(true))),owner:true))); Check.That(merged.NativeFilterFacts is{State.Key:"ActualState",ColonisationOwnership.Own:true,BasicSystemRequested:true}); merged=merged.Merge(Data(await Read(Row(("basicSystem",null)),basic:true))); Check.That(merged.NativeFilterFacts is{State.Key:"ActualState",ColonisationOwnership.Own:true,BasicSystemRequested:true,BasicSystem:null}); merged=merged.Merge(Data(await Read(Row(("state",null)),state:true))); Check.That(merged.NativeFilterFacts is{StateRequested:true,State:null,ColonisationOwnership.Own:true}); merged=merged.Merge(Data(await Read(Row(("colonisationOwnership",null)),owner:true))); Check.That(merged.NativeFilterFacts is{StateRequested:true,State:null,ColonisationOwnershipRequested:true,ColonisationOwnership:null,BasicSystemRequested:true,BasicSystem:null}); } [Test] public async Task ChangedAccountProcessOrOriginCannotInheritAnyUnrequestedClassifier() { var initial=Data(await Read(Row(("state",State()),("colonisationOwnership",Owner()),("basicSystem",Basic())),true,true,true)); var unrelated=await Read(Row()); foreach(var context in new[]{(Account:"F2",Pid:42,Origin:42L),(Account:"F1",Pid:43,Origin:42L),(Account:"F1",Pid:42,Origin:43L)}) { 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{StateRequested:false,State:null,ColonisationOwnershipRequested:false,ColonisationOwnership:null,BasicSystemRequested:false,BasicSystem:null}); } } }