using System.IO.Pipes; using System.Text.Json; using XFEEDToolBox.Core.Galaxy; using XFEExtension.NetCore.XUnit.Attributes; namespace XFEEDToolBox.Test; [TestFixture] public class NativeGalaxyCellsTests { private static async Task Serve(int id,object result,CancellationToken token,Action? inspect=null) { await using var server=new NamedPipeServerStream($"XFEEDToolBox.Navigation.{id}",PipeDirection.InOut,1,PipeTransmissionMode.Byte,PipeOptions.Asynchronous); await server.WaitForConnectionAsync(token);var buffer=new byte[65536];var size=await server.ReadAsync(buffer,token); using(var request=JsonDocument.Parse(buffer.AsMemory(0,size)))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(); } [Test] public async Task ExactCellsPublishRealRowsBeforeDoneAndKeepTheWholeCellCorner() { var id=Random.Shared.Next(100000000,110000000);using var deadline=new CancellationTokenSource(TimeSpan.FromSeconds(5)); var completed=false;var partialBeforeDone=false;var address=(1UL<<44)|7; var server=Task.Run(async()=> { await Serve(id,new{generation=7,done=false,error="",total=1,items=new[]{new{name="Cell corner",systemAddress=address.ToString(),position=new[]{1000d,1000d,1000d},starClass="M"}}, scope="requested-cells",requestedCells=1,minimumMassCode=7,processedCells=0,totalCells=1,failedCells=0,truncated=false,complete=false,origin=new[]{1d,2d,3d}},deadline.Token,r=> {Check.Equal("galaxy.cells",r.GetProperty("op").GetString());Check.Equal("7",r.GetProperty("cellAddresses")[0].GetString());Check.Equal(8,r.GetProperty("radii").GetArrayLength());}); await Serve(id,new{generation=7,done=true,error="",total=1,items=Array.Empty(),scope="requested-cells",requestedCells=1, minimumMassCode=7,processedCells=1,totalCells=1,failedCells=0,truncated=false,complete=true,origin=new[]{1d,2d,3d}},deadline.Token,r=> {Check.Equal(1,r.GetProperty("offset").GetInt32());completed=true;}); }); var catalog=new NativeGalaxyCatalog(id);catalog.RegionProgress+=progress=> {if(progress.Systems.Count>0)partialBeforeDone=!completed&&!progress.Done;}; var result=await catalog.CellsAsync(new(0,0,0),Enumerable.Repeat(1d,8).ToArray(),new[]{7UL},deadline.Token);await server; Check.That(partialBeforeDone);Check.Equal(1,result.Systems.Count);Check.Equal(1000d,result.Systems[0].Position.X); Check.That(result.AreRequestedCellsCovered&&!result.IsFullyCovered);Check.Equal(7,result.MinimumMassCode); Check.Equal(2d,result.Origin!.Y); } [Test] public void RequestedMassSevenCoverageDoesNotClaimAllMassSphereCoverage() { using var json=JsonDocument.Parse("{\"scope\":\"requested-cells\",\"requestedCells\":1,\"minimumMassCode\":7,\"processedCells\":1,\"totalCells\":1,\"failedCells\":0,\"truncated\":false,\"complete\":true}"); var result=NativeGalaxyCatalog.ReadCoverage(json.RootElement,[]); Check.That(result.AreRequestedCellsCovered&&!result.IsFullyCovered);Check.Equal(1,result.RequestedCells); using var changed=JsonDocument.Parse("{\"scope\":\"requested-cells\",\"requestedCells\":2,\"minimumMassCode\":7,\"processedCells\":1,\"totalCells\":1}"); Check.Throws(()=>NativeGalaxyCatalog.ReadCoverage(changed.RootElement,[])); } [Test] public void CellRequestRejectsNoncanonicalAddressesAndUnsupportedKnownProtocolBeforeIpc() { var catalog=new NativeGalaxyCatalog(123);var radii=Enumerable.Repeat(1d,8).ToArray(); Check.Throws(()=>catalog.CellsAsync(new(0,0,0),radii,new[]{1UL<<44})); Check.Throws(()=>catalog.CellsAsync(new(0,0,0),radii,new[]{7UL,7UL})); using var description=JsonDocument.Parse("{\"processId\":123}");catalog.ApplyProtocolDescription(description.RootElement); Check.Throws(()=>catalog.CellsAsync(new(0,0,0),radii,new[]{7UL})); } [Test] public async Task CellPagesRejectMissingOrChangedOriginsAndConflictingStarIdentities() { for(var scenario=0;scenario<4;scenario++) { var id=Random.Shared.Next(114000000,115000000);using var deadline=new CancellationTokenSource(TimeSpan.FromSeconds(5)); var variant=scenario;var address=1UL<<44; object Reply(bool first) { var origin=variant==0&&first||variant==2&&!first?null:new[]{variant==1&&!first?4d:1d,2d,3d}; var row=new{name=variant==3&&!first?"Conflicting name":"Fixture star",systemAddress=address.ToString(),position=new[]{0d,0d,0d},starClass="M"}; return new{generation=72,done=!first,error="",total=2,items=new[]{row},scope="requested-cells",requestedCells=1, minimumMassCode=0,processedCells=first?0:1,totalCells=1,failedCells=0,truncated=false,complete=!first,origin}; } var server=Task.Run(async()=> { await Serve(id,Reply(true),deadline.Token); if(variant!=0)await Serve(id,Reply(false),deadline.Token); await Serve(id,new{generation=72,done=true},deadline.Token,r=>Check.Equal("galaxy.cancel",r.GetProperty("op").GetString())); await Serve(id,new{generation=72,done=true},deadline.Token,r=>Check.Equal("galaxy.result",r.GetProperty("op").GetString())); }); try { await new NativeGalaxyCatalog(id).CellsAsync(new(0,0,0),Enumerable.Repeat(1d,8).ToArray(),new[]{0UL},deadline.Token); throw new Exception("Invalid cell page was accepted"); } catch(InvalidDataException){} await server; } } }