using XFEEDToolBox.Core.Execution;
using XFEEDToolBox.Core.Models;
using XFEExtension.NetCore.XUnit.Attributes;
namespace XFEEDToolBox.Test;
[TestFixture]
public sealed class TradeRunMenuScopeTests
{
private static PlayerState Player=>new(){Ship="Type9",InGame=true,Docked=true,MarketId=1,Credits=10000,CreditsReliable=true,CargoCapacity=10,StatusAt=DateTimeOffset.UtcNow};
private static RoutePlan Plan=>new(){Request=Check.Request with{MaxLegs=1},Legs=[new(){Number=1,From=Check.Station(1),To=Check.Station(2),Orders=[new("gold","Gold",3,100,180)]}]};
private sealed class Lease(Func<bool> running):IDisposable
{
public bool Disposed;public bool Allowed=>running();
public void Dispose(){Disposed=true;Check.That(!Allowed,"Permission must be revoked before disposing its scope.");}
}
private sealed class Driver:ITradeCommandDriver,ITradeRunMenuRecovery
{
public List<Lease> Leases=[];public bool Released,RejectPreflight,RejectScope,RejectAction;public Action? BeforeScopeReturned;
public IDisposable EnterRunningMenuRecovery(Func<bool> isRunning,CancellationToken token)
{
if(RejectScope)throw new InvalidOperationException("scope fixture failure");
var scope=new Lease(isRunning);Leases.Add(scope);Check.That(scope.Allowed);BeforeScopeReturned?.Invoke();return scope;
}
public Task PreflightAsync(CancellationToken token){if(RejectPreflight)throw new InvalidOperationException("preflight fixture failure");return Task.CompletedTask;}
public Task ServiceDockedAsync(DockedServiceRequest request,Func<CancellationToken,Task> beforeInput,CancellationToken token)=>beforeInput(token);
public async Task ExecuteAsync(TradeAction action,Func<CancellationToken,Task> beforeInput,CancellationToken token)
{await beforeInput(token);if(RejectAction)throw new InvalidOperationException("action fixture failure");await Task.Delay(Timeout.Infinite,token);}
public Task ReleaseAllAsync(){Released=true;return Task.CompletedTask;}
}
private static TradeRunner Runner(Driver driver)=>new(()=>Player,(id,key)=>Check.Quote(id,key,id==1?100:200,id==1?90:180));
[Test]public async Task NativeRunningPauseResumeAndStopOwnDifferentRevocableScopes()
{
var driver=new Driver();var runner=Runner(driver);var checkedState=false;
runner.Progress+=progress=>
{
if(checkedState||!progress.Message.StartsWith("开始执行"))return;checkedState=true;
var first=driver.Leases.Single();Check.That(first.Allowed&&!first.Disposed);
runner.Pause();Check.Equal(RunState.Paused,runner.State);Check.That(first.Disposed&&!first.Allowed);
runner.Resume();Check.Equal(RunState.Running,runner.State);Check.Equal(2,driver.Leases.Count);
var second=driver.Leases[1];Check.That(second.Allowed&&!second.Disposed&&!first.Allowed);
runner.Stop();Check.That(second.Disposed&&!second.Allowed);runner.Resume();Check.Equal(2,driver.Leases.Count);
};
await runner.RunAsync(Plan,ExecutionMode.NativeBridge,driver).WaitAsync(TimeSpan.FromSeconds(2));
Check.That(checkedState&&driver.Released);Check.Equal(RunState.Stopped,runner.State);Check.That(driver.Leases.All(lease=>lease.Disposed&&!lease.Allowed));
}
[Test]public async Task GuideSimulationAndRejectedPreflightNeverAuthorizeMenuActions()
{
foreach(var mode in new[]{ExecutionMode.JournalGuide,ExecutionMode.Simulation,ExecutionMode.NativeBridge})
{
var driver=new Driver{RejectPreflight=mode==ExecutionMode.NativeBridge};var runner=Runner(driver);
runner.Progress+=progress=>{if(progress.Message.StartsWith("开始执行"))runner.Stop();};
await runner.RunAsync(Plan,mode,driver).WaitAsync(TimeSpan.FromSeconds(2));
Check.Equal(0,driver.Leases.Count);Check.That(driver.Released);
}
}
[Test]public async Task CallerCancellationRevokesPermissionBeforeRunUnwinds()
{
using var cancellation=new CancellationTokenSource();var driver=new Driver();var runner=Runner(driver);var observed=false;
runner.Progress+=progress=>
{
if(observed||!progress.Message.StartsWith("开始执行"))return;observed=true;
Check.That(driver.Leases.Single().Allowed);cancellation.Cancel();Check.That(!driver.Leases.Single().Allowed);
};
await runner.RunAsync(Plan,ExecutionMode.NativeBridge,driver,cancellation.Token).WaitAsync(TimeSpan.FromSeconds(2));
Check.That(observed&&driver.Leases.Single().Disposed&&driver.Released);Check.Equal(RunState.Stopped,runner.State);
}
[Test]public async Task DriverFailureAndScopeCreationFailureReleasePermissionAndControls()
{
foreach(var rejectScope in new[]{false,true})
{
var driver=new Driver{RejectScope=rejectScope,RejectAction=!rejectScope};var runner=Runner(driver);
await runner.RunAsync(Plan,ExecutionMode.NativeBridge,driver).WaitAsync(TimeSpan.FromSeconds(2));
Check.Equal(RunState.Failed,runner.State);Check.That(driver.Released);
Check.That(driver.Leases.All(lease=>lease.Disposed&&!lease.Allowed));
}
}
[Test]public async Task PauseDuringScopeCreationRejectsTheLateScopeAndRunningNotification()
{
var driver=new Driver();var runner=Runner(driver);var paused=false;var lateRunning=false;
driver.BeforeScopeReturned=()=>runner.Pause();
runner.Progress+=progress=>
{
if(progress.State==RunState.Paused){paused=true;runner.Stop();}
if(paused&&progress.State==RunState.Running)lateRunning=true;
};
await runner.RunAsync(Plan,ExecutionMode.NativeBridge,driver).WaitAsync(TimeSpan.FromSeconds(2));
Check.That(paused&&!lateRunning&&driver.Released);Check.Equal(1,driver.Leases.Count);Check.That(driver.Leases[0].Disposed&&!driver.Leases[0].Allowed);
Check.Equal(RunState.Stopped,runner.State);
}
}
using XFEEDToolBox.Core.Execution;
using XFEEDToolBox.Core.Models;
using XFEExtension.NetCore.XUnit.Attributes;
namespace XFEEDToolBox.Test;
[TestFixture]
public sealed class TradeRunMenuScopeTests
{
private static PlayerState Player=>new(){Ship="Type9",InGame=true,Docked=true,MarketId=1,Credits=10000,CreditsReliable=true,CargoCapacity=10,StatusAt=DateTimeOffset.UtcNow};
private static RoutePlan Plan=>new(){Request=Check.Request with{MaxLegs=1},Legs=[new(){Number=1,From=Check.Station(1),To=Check.Station(2),Orders=[new("gold","Gold",3,100,180)]}]};
private sealed class Lease(Func<bool> running):IDisposable
{
public bool Disposed;public bool Allowed=>running();
public void Dispose(){Disposed=true;Check.That(!Allowed,"Permission must be revoked before disposing its scope.");}
}
private sealed class Driver:ITradeCommandDriver,ITradeRunMenuRecovery
{
public List<Lease> Leases=[];public bool Released,RejectPreflight,RejectScope,RejectAction;public Action? BeforeScopeReturned;
public IDisposable EnterRunningMenuRecovery(Func<bool> isRunning,CancellationToken token)
{
if(RejectScope)throw new InvalidOperationException("scope fixture failure");
var scope=new Lease(isRunning);Leases.Add(scope);Check.That(scope.Allowed);BeforeScopeReturned?.Invoke();return scope;
}
public Task PreflightAsync(CancellationToken token){if(RejectPreflight)throw new InvalidOperationException("preflight fixture failure");return Task.CompletedTask;}
public Task ServiceDockedAsync(DockedServiceRequest request,Func<CancellationToken,Task> beforeInput,CancellationToken token)=>beforeInput(token);
public async Task ExecuteAsync(TradeAction action,Func<CancellationToken,Task> beforeInput,CancellationToken token)
{await beforeInput(token);if(RejectAction)throw new InvalidOperationException("action fixture failure");await Task.Delay(Timeout.Infinite,token);}
public Task ReleaseAllAsync(){Released=true;return Task.CompletedTask;}
}
private static TradeRunner Runner(Driver driver)=>new(()=>Player,(id,key)=>Check.Quote(id,key,id==1?100:200,id==1?90:180));
[Test]public async Task NativeRunningPauseResumeAndStopOwnDifferentRevocableScopes()
{
var driver=new Driver();var runner=Runner(driver);var checkedState=false;
runner.Progress+=progress=>
{
if(checkedState||!progress.Message.StartsWith("开始执行"))return;checkedState=true;
var first=driver.Leases.Single();Check.That(first.Allowed&&!first.Disposed);
runner.Pause();Check.Equal(RunState.Paused,runner.State);Check.That(first.Disposed&&!first.Allowed);
runner.Resume();Check.Equal(RunState.Running,runner.State);Check.Equal(2,driver.Leases.Count);
var second=driver.Leases[1];Check.That(second.Allowed&&!second.Disposed&&!first.Allowed);
runner.Stop();Check.That(second.Disposed&&!second.Allowed);runner.Resume();Check.Equal(2,driver.Leases.Count);
};
await runner.RunAsync(Plan,ExecutionMode.NativeBridge,driver).WaitAsync(TimeSpan.FromSeconds(2));
Check.That(checkedState&&driver.Released);Check.Equal(RunState.Stopped,runner.State);Check.That(driver.Leases.All(lease=>lease.Disposed&&!lease.Allowed));
}
[Test]public async Task GuideSimulationAndRejectedPreflightNeverAuthorizeMenuActions()
{
foreach(var mode in new[]{ExecutionMode.JournalGuide,ExecutionMode.Simulation,ExecutionMode.NativeBridge})
{
var driver=new Driver{RejectPreflight=mode==ExecutionMode.NativeBridge};var runner=Runner(driver);
runner.Progress+=progress=>{if(progress.Message.StartsWith("开始执行"))runner.Stop();};
await runner.RunAsync(Plan,mode,driver).WaitAsync(TimeSpan.FromSeconds(2));
Check.Equal(0,driver.Leases.Count);Check.That(driver.Released);
}
}
[Test]public async Task CallerCancellationRevokesPermissionBeforeRunUnwinds()
{
using var cancellation=new CancellationTokenSource();var driver=new Driver();var runner=Runner(driver);var observed=false;
runner.Progress+=progress=>
{
if(observed||!progress.Message.StartsWith("开始执行"))return;observed=true;
Check.That(driver.Leases.Single().Allowed);cancellation.Cancel();Check.That(!driver.Leases.Single().Allowed);
};
await runner.RunAsync(Plan,ExecutionMode.NativeBridge,driver,cancellation.Token).WaitAsync(TimeSpan.FromSeconds(2));
Check.That(observed&&driver.Leases.Single().Disposed&&driver.Released);Check.Equal(RunState.Stopped,runner.State);
}
[Test]public async Task DriverFailureAndScopeCreationFailureReleasePermissionAndControls()
{
foreach(var rejectScope in new[]{false,true})
{
var driver=new Driver{RejectScope=rejectScope,RejectAction=!rejectScope};var runner=Runner(driver);
await runner.RunAsync(Plan,ExecutionMode.NativeBridge,driver).WaitAsync(TimeSpan.FromSeconds(2));
Check.Equal(RunState.Failed,runner.State);Check.That(driver.Released);
Check.That(driver.Leases.All(lease=>lease.Disposed&&!lease.Allowed));
}
}
[Test]public async Task PauseDuringScopeCreationRejectsTheLateScopeAndRunningNotification()
{
var driver=new Driver();var runner=Runner(driver);var paused=false;var lateRunning=false;
driver.BeforeScopeReturned=()=>runner.Pause();
runner.Progress+=progress=>
{
if(progress.State==RunState.Paused){paused=true;runner.Stop();}
if(paused&&progress.State==RunState.Running)lateRunning=true;
};
await runner.RunAsync(Plan,ExecutionMode.NativeBridge,driver).WaitAsync(TimeSpan.FromSeconds(2));
Check.That(paused&&!lateRunning&&driver.Released);Check.Equal(1,driver.Leases.Count);Check.That(driver.Leases[0].Disposed&&!driver.Leases[0].Allowed);
Check.Equal(RunState.Stopped,runner.State);
}
}