using System.Text.Json; using XFEEDToolBox.Core.Models; using XFEEDToolBox.Core.Session; using XFEExtension.NetCore.XUnit.Attributes; namespace XFEEDToolBox.Test; [TestFixture]public class SessionMaintenanceFlightGateTests { private static readonly DateTimeOffset Now=DateTimeOffset.Parse("2026-10-25T19:00:00Z"); private static PlayerState Ship(DateTimeOffset at)=>new(){CommanderId="maintenance",CurrentShipId=8,CargoCapacity=1200, InGame=true,LoadGameAt=Now.AddHours(-1),CargoReliable=true,CreditsReliable=true,CargoCount=0,StatusReadAt=at,Ships=[new(){ShipId=8,HullHealth=1}]}; private static JsonElement Raw(DateTimeOffset at,bool health=true,bool damage=false,bool emergency=false,double hull=1,bool manual=false,string entity="7",string world="9",long healthSequence=1) =>Check.Event(new{sequence=1,observedAtUnixMilliseconds=at.ToUnixTimeMilliseconds(),entityId=entity,referenceWorldAddress=world, inGame=true,localPlayerShipVerified=true,playerOverride=manual,shipHealth=new{sequence=healthSequence,observedAtUnixMilliseconds=at.ToUnixTimeMilliseconds(), entityId=entity,verified=health,damageObserved=damage,emergency,hullFraction=hull}}); private static JsonElement Local(DateTimeOffset at,double speed=0,double throttle=0,int fsd=2,bool sc=false,bool duty=false,string entity="7",string world="9") =>Check.Event(new{sequence=1,observedAtUnixMilliseconds=at.ToUnixTimeMilliseconds(),entityId=entity,referenceWorldAddress=world, rawFsdState=fsd,inSupercruise=sc,speedMetresPerSecond=speed,throttle,docking=new{verified=true,nativeDutyActive=duty,nativeLaunchActive=false}}); [Test]public void EmptyStationaryHealthyShipRequiresTenContinuousSecondsBeforeMenuUpdate() { var gate=new SessionMaintenanceFlightGate(Ship(Now));Check.That(!gate.Observe(Ship(Now),Raw(Now),Local(Now),Now)); var at=Now.AddSeconds(9);Check.That(!gate.Observe(Ship(at),Raw(at),Local(at),at)); at=Now.AddSeconds(10);Check.That(gate.Observe(Ship(at),Raw(at),Local(at),at)); } [Test]public void CargoUnreliableStateAndExternalThreatCannotAuthorizeSpaceUpdate() { var at=Now.AddSeconds(10); foreach(var current in new[]{Ship(at) with{CargoCount=1},Ship(at) with{CargoReliable=false},Ship(at) with{CreditsReliable=false}, Ship(at) with{StatusReadAt=at.AddSeconds(-3)},Ship(at) with{Navigation=new(){ExternalThreat=true}},Ship(at) with{Docked=true}}) { var gate=new SessionMaintenanceFlightGate(Ship(Now));gate.Observe(Ship(Now),Raw(Now),Local(Now),Now); Check.That(!gate.Observe(current,Raw(at),Local(at),at)); Check.That(!gate.Observe(Ship(at),Raw(at),Local(at),at)); } } [Test]public void NativeDamageMotionActiveDutyAndMismatchedOrStaleFramesResetWaiting() { var at=Now.AddSeconds(10); foreach(var (raw,local) in new[]{(Raw(at,health:false),Local(at)),(Raw(at,damage:true),Local(at)),(Raw(at,emergency:true),Local(at)), (Raw(at,hull:.8),Local(at)),(Raw(at),Local(at,speed:1)),(Raw(at),Local(at,throttle:.1)),(Raw(at),Local(at,fsd:3)), (Raw(at),Local(at,sc:true)),(Raw(at),Local(at,duty:true)),(Raw(at),Local(at,entity:"8")),(Raw(at),Local(at,world:"10")), (Raw(at.AddSeconds(-1)),Local(at)),(Raw(at),Local(at.AddSeconds(-1))),(Raw(at,healthSequence:2),Local(at))}) { var gate=new SessionMaintenanceFlightGate(Ship(Now));gate.Observe(Ship(Now),Raw(Now),Local(Now),Now); Check.That(!gate.Observe(Ship(at),raw,local,at));Check.That(!gate.Observe(Ship(at),Raw(at),Local(at),at)); } } [Test]public void IdentitySessionChangesAndPhysicalTakeoverRemainTerminal() { foreach(var current in new[]{Ship(Now) with{CommanderId="other"},Ship(Now) with{CurrentShipId=9},Ship(Now) with{CargoCapacity=2000},Ship(Now) with{LoadGameAt=Now}}) Check.Throws(()=>new SessionMaintenanceFlightGate(Ship(Now)).Observe(current,Raw(Now),Local(Now),Now)); Check.Throws(()=>new SessionMaintenanceFlightGate(Ship(Now)).Observe(Ship(Now),Raw(Now,manual:true),Local(Now),Now)); } [Test]public void ChangedEntityOrWorldNeedsANewCompleteStabilityWindow() { var gate=new SessionMaintenanceFlightGate(Ship(Now));gate.Observe(Ship(Now),Raw(Now),Local(Now),Now); var at=Now.AddSeconds(10);Check.That(!gate.Observe(Ship(at),Raw(at,entity:"8",world:"10"),Local(at,entity:"8",world:"10"),at)); at=Now.AddSeconds(20);Check.That(gate.Observe(Ship(at),Raw(at,entity:"8",world:"10"),Local(at,entity:"8",world:"10"),at)); } [Test]public void StableHistoricalHullCanExitForMaintenanceButAnyFurtherDecreaseRestartsTheWindow() { var gate=new SessionMaintenanceFlightGate(Ship(Now)); Check.That(!gate.Observe(Ship(Now),Raw(Now,hull:.502883),Local(Now),Now)); var at=Now.AddSeconds(10);Check.That(gate.Observe(Ship(at),Raw(at,hull:.502883),Local(at),at)); at=Now.AddSeconds(11);Check.That(!gate.Observe(Ship(at),Raw(at,hull:.50),Local(at),at)); at=Now.AddSeconds(20);Check.That(!gate.Observe(Ship(at),Raw(at,hull:.50),Local(at),at)); at=Now.AddSeconds(21);Check.That(gate.Observe(Ship(at),Raw(at,hull:.50),Local(at),at)); foreach(var hull in new[]{0d,-.1,1.1}) Check.That(!new SessionMaintenanceFlightGate(Ship(Now)).Observe(Ship(at),Raw(at,hull:hull),Local(at),at)); } }