using System.IO.Pipes; using System.Text.Json; using XFEEDToolBox.Core.Galaxy; using XFEExtension.NetCore.XUnit.Attributes; namespace XFEEDToolBox.Test; [TestFixture] public sealed class NativeGalaxyAccountCatalogTests { 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 request=new byte[4096];var count=await server.ReadAsync(request,token); using(var json=JsonDocument.Parse(request.AsMemory(0,count)))inspect?.Invoke(json.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 object Row(int i)=>new{uid=(9007199254740993UL+(ulong)i).ToString(),systemAddress=(9007199254740993L+i).ToString(),systemName="Actual recorded name "+i,name="Private label "+i,sourceKind=i%2+1,favourite=i%2==0}; [Test] public async Task ThargoidCacheKeepsActualAddressesRecommendationOrderAndUnknownReadiness() { var pid=Random.Shared.Next(170000000,180000000);using var stop=new CancellationTokenSource(TimeSpan.FromSeconds(5)); var server=Task.Run(()=>Serve(pid,new{available=true,generation=9,total=3,entityId="42",metadataObserved=false,responseObserved=true, states=Enumerable.Range(33,6).Select(s=>new{state=s,count=(uint?)null}),items=new object[]{ new{kind="signal",id=0,systemAddress="9007199254740993",stronghold=false,affectedSystemsCount=(int?)null}, new{kind="recommended",id=33<<8,state=33,rank=0,systemAddress="9007199254740994",progress=.8f}, new{kind="recommended",id=33<<8|1,state=33,rank=1,systemAddress="9007199254740995",progress=.3f}}},stop.Token, r=>Check.Equal("galaxy.thargoid",r.GetProperty("op").GetString()))); var result=await new NativeGalaxyAccountCatalog(pid).ReadThargoidAsync(stop.Token);await server; Check.That(result.Available&&!result.Ready&&!result.Complete);Check.That(result.States.All(s=>s.Count is null)); Check.Equal(9007199254740993L,result.Signals[0].SystemAddress);Check.That(!result.Signals[0].Stronghold); Check.Equal(2,result.Recommended.Count);Check.Equal(.8f,result.Recommended[0].Progress);Check.Equal(1,result.Recommended[1].Rank); } [Test] public async Task ThargoidUnreadyCountCannotBeReportedAsZeroAndUiIdsCannotBecomeAddresses() { var pid=Random.Shared.Next(170000000,180000000);using var stop=new CancellationTokenSource(TimeSpan.FromSeconds(5)); var server=Task.Run(()=>Serve(pid,new{available=true,generation=9,total=0,entityId="42",metadataObserved=false,responseObserved=true, states=Enumerable.Range(33,6).Select(s=>new{state=s,count=(uint?)0}),items=Array.Empty()},stop.Token)); var rejected=false;try{await new NativeGalaxyAccountCatalog(pid).ReadThargoidAsync(stop.Token);}catch(InvalidDataException){rejected=true;} await server;Check.That(rejected); server=Task.Run(()=>Serve(pid,new{available=true,generation=9,total=1,entityId="42",metadataObserved=true,responseObserved=true, states=Enumerable.Range(33,6).Select(s=>new{state=s,count=(uint?)0}),items=new[]{new{kind="recommended",id=0,state=33,rank=0,systemAddress="9007199254740993",progress=.5f}}},stop.Token)); rejected=false;try{await new NativeGalaxyAccountCatalog(pid).ReadThargoidAsync(stop.Token);}catch(InvalidDataException){rejected=true;} await server;Check.That(rejected); } [Test] public async Task PowerDefinitionsKeepInternalAndUiIdsSeparateAndRetainHdrValues() { var pid=Random.Shared.Next(170000000,180000000);using var stop=new CancellationTokenSource(TimeSpan.FromSeconds(5)); var server=Task.Run(()=>Serve(pid,new{available=true,optionsComplete=true,generation=9,total=2,entityId="42",items=new[]{ new{index=0,uiPowerId=1,internalPowerId=17,name="First recorded definition",rawRed=12f,rawGreen=.25f,rawBlue=0f}, new{index=1,uiPowerId=2,internalPowerId=4,name="Second recorded definition",rawRed=0f,rawGreen=1f,rawBlue=0f}}},stop.Token,r=>Check.Equal("galaxy.powers",r.GetProperty("op").GetString()))); var result=await new NativeGalaxyAccountCatalog(pid).ReadPowerOptionsAsync(stop.Token);await server; Check.That(result.Available&&result.OptionsComplete);Check.Equal(1,result.Options[0].UiPowerId);Check.Equal(17,result.Options[0].InternalPowerId);Check.Equal(12f,result.Options[0].RawRed); Check.Equal(4,result.Options[1].InternalPowerId); } [Test] public async Task CommunityGoalMapListPreservesDuplicateSystemsAndDoesNotCertifyCompleteness() { var pid=Random.Shared.Next(170000000,180000000);using var stop=new CancellationTokenSource(TimeSpan.FromSeconds(5)); var server=Task.Run(()=>Serve(pid,new{available=true,generation=9,total=3,entityId="42",items=new[]{"9007199254740993","9007199254740994","9007199254740993"}},stop.Token, r=>Check.Equal("galaxy.communitygoals",r.GetProperty("op").GetString()))); var result=await new NativeGalaxyAccountCatalog(pid).ReadCommunityGoalsAsync(stop.Token);await server; Check.That(result.Available&&!result.Complete);Check.Equal(3,result.SystemAddresses.Count);Check.Equal(9007199254740993L,result.SystemAddresses[0]);Check.Equal(result.SystemAddresses[0],result.SystemAddresses[2]); } [Test] public async Task InvalidOriginalPowerIndexCannotSelectAnUnrelatedDefinition() { var pid=Random.Shared.Next(170000000,180000000);using var stop=new CancellationTokenSource(TimeSpan.FromSeconds(5)); var server=Task.Run(()=>Serve(pid,new{available=true,optionsComplete=true,generation=9,total=1,entityId="42",items=new[]{ new{index=0,uiPowerId=17,internalPowerId=17,name="Definition",rawRed=1f,rawGreen=0f,rawBlue=0f}}},stop.Token)); var rejected=false;try{await new NativeGalaxyAccountCatalog(pid).ReadPowerOptionsAsync(stop.Token);}catch(InvalidDataException){rejected=true;} await server;Check.That(rejected); } [Test] public async Task SocialCachePreservesUnknownLocationCurrentAndIndependentSourceReadiness() { var pid=Random.Shared.Next(170000000,180000000);using var stop=new CancellationTokenSource(TimeSpan.FromSeconds(5)); var server=Task.Run(()=>Serve(pid,new{available=true,generation=9,total=2,entityId="42",friendsReady=true,wingsReady=false,items=new[]{ new{systemAddress="9007199254740993",kind="friend",status=0,statusName="offline",locationCurrent=(bool?)null}, new{systemAddress="9007199254740994",kind="friend",status=6,statusName="open",locationCurrent=(bool?)true}}},stop.Token,r=>Check.Equal("galaxy.social",r.GetProperty("op").GetString()))); var result=await new NativeGalaxyAccountCatalog(pid).ReadSocialAsync(stop.Token);await server; Check.That(result.Available&&result.FriendsReady&&!result.WingsReady&&!result.Complete);Check.Equal(2,result.Locations.Count); Check.Equal(9007199254740993L,result.Locations[0].SystemAddress);Check.That(result.Locations[0].LocationCurrent is null&&result.Locations[1].LocationCurrent==true); } [Test] public async Task SocialPagesCannotCrossOwnerOrSourceReadinessChanges() { var pid=Random.Shared.Next(170000000,180000000);using var stop=new CancellationTokenSource(TimeSpan.FromSeconds(5)); object Row()=>new{systemAddress="9007199254740993",kind="friend",status=6,statusName="open",locationCurrent=(bool?)null}; var server=Task.Run(async()=> { await Serve(pid,new{available=true,generation=9,total=97,entityId="42",friendsReady=true,wingsReady=false,items=Enumerable.Range(0,96).Select(_=>Row())},stop.Token); await Serve(pid,new{available=true,generation=9,total=97,entityId="42",friendsReady=false,wingsReady=true,items=new[]{Row()}},stop.Token, r=>Check.Equal(96,r.GetProperty("offset").GetInt32())); }); var rejected=false;try{await new NativeGalaxyAccountCatalog(pid).ReadSocialAsync(stop.Token);}catch(InvalidDataException){rejected=true;} await server;Check.That(rejected); } [Test] public async Task ColonisationCopiesExactCommanderAndStatusWithoutGuessingOwnershipOrAbsence() { var pid=Random.Shared.Next(170000000,180000000);using var stop=new CancellationTokenSource(TimeSpan.FromSeconds(5)); var server=Task.Run(()=>Serve(pid,new{available=true,generation=9,total=2,entityId="42",items=new[]{ new{systemAddress="9007199254740993",commanderId="9007199254740995",status=3,active=true}, new{systemAddress="9007199254740994",commanderId="123",status=0,active=false}}},stop.Token,r=>Check.Equal("galaxy.colonisation",r.GetProperty("op").GetString()))); var result=await new NativeGalaxyAccountCatalog(pid).ReadColonisationAsync(stop.Token);await server; Check.That(result.Available&&!result.Complete);Check.Equal(2,result.Claims.Count); Check.Equal(9007199254740995UL,result.Claims[0].CommanderId);Check.That(result.Claims[0].Active&&!result.Claims[1].Active); Check.Equal(9007199254740993L,result.Claims[0].SystemAddress); } [Test] public async Task ContradictoryColonisationStatusCannotBecomeAPersonalClaim() { var pid=Random.Shared.Next(170000000,180000000);using var stop=new CancellationTokenSource(TimeSpan.FromSeconds(5)); var server=Task.Run(()=>Serve(pid,new{available=true,generation=9,total=1,entityId="42",items=new[]{ new{systemAddress="9007199254740993",commanderId="123",status=0,active=true}}},stop.Token)); var rejected=false;try{await new NativeGalaxyAccountCatalog(pid).ReadColonisationAsync(stop.Token);}catch(InvalidDataException){rejected=true;} await server;Check.That(rejected); } [Test] public async Task ExactLargeAddressesAndBothBookmarkSourcesSurviveStablePagination() { var pid=Random.Shared.Next(170000000,180000000);using var stop=new CancellationTokenSource(TimeSpan.FromSeconds(5)); var server=Task.Run(async()=> { await Serve(pid,new{available=true,generation=7,total=17,entityId="42",items=Enumerable.Range(0,16).Select(Row)},stop.Token, r=>{Check.Equal("galaxy.account",r.GetProperty("op").GetString());Check.Equal(16,r.GetProperty("limit").GetInt32());}); await Serve(pid,new{available=true,generation=7,total=17,entityId="42",items=new[]{Row(16)}},stop.Token, r=>{Check.Equal(7,r.GetProperty("generation").GetInt32());Check.Equal(16,r.GetProperty("offset").GetInt32());}); }); var result=await new NativeGalaxyAccountCatalog(pid).ReadAsync(stop.Token);await server; Check.That(result.Available&&!result.Complete);Check.Equal(17,result.Bookmarks.Count);Check.Equal(9007199254740993L,result.Bookmarks[0].SystemAddress); Check.That(!result.Bookmarks[0].Squadron&&result.Bookmarks[1].Squadron); } [Test] public async Task AChangedOwnerOrGenerationCannotMergeDifferentAccountSnapshots() { var pid=Random.Shared.Next(170000000,180000000);using var stop=new CancellationTokenSource(TimeSpan.FromSeconds(5)); var server=Task.Run(async()=> { await Serve(pid,new{available=true,generation=8,total=17,entityId="42",items=Enumerable.Range(0,16).Select(Row)},stop.Token); await Serve(pid,new{available=true,generation=9,total=17,entityId="43",items=new[]{Row(16)}},stop.Token); }); var rejected=false;try{await new NativeGalaxyAccountCatalog(pid).ReadAsync(stop.Token);}catch(InvalidDataException){rejected=true;} await server;Check.That(rejected); } [Test] public async Task UnavailableAndEmptySnapshotsCannotProveNoAccountBookmarksExist() { var pid=Random.Shared.Next(170000000,180000000);using var stop=new CancellationTokenSource(TimeSpan.FromSeconds(5)); var server=Task.Run(()=>Serve(pid,new{available=false},stop.Token));var absent=await new NativeGalaxyAccountCatalog(pid).ReadAsync(stop.Token);await server; Check.That(!absent.Available&&!absent.Complete&&absent.Bookmarks.Count==0); server=Task.Run(()=>Serve(pid,new{available=true,generation=1,total=0,entityId="42",items=Array.Empty()},stop.Token)); var empty=await new NativeGalaxyAccountCatalog(pid).ReadAsync(stop.Token);await server;Check.That(empty.Available&&!empty.Complete&&empty.Bookmarks.Count==0); } }