using System.Text.Json;
using XFEEDToolBox.Core.Execution;
using XFEEDToolBox.Core.Models;
using XFEExtension.NetCore.XUnit.Attributes;
namespace XFEEDToolBox.Test;
[TestFixture]
public sealed class StationAccessWaiterTests
{
[Test]public async Task OneJournalPassResolvesAllKnownMarketsForThisFidAndActualPaymentRemovesTheirFineExclusion()
{
using var directory=new TemporaryDirectory();var rows=new List<object>{new{@event="LoadGame",FID="F1"}};
for(var market=1;market<=100;market++)rows.Add(new{@event="Docked",MarketID=market,StationFaction=new{Name="Authority"}});
rows.Add(new{@event="CommitCrime",Faction="Authority",Fine=400});
rows.Add(new{@event="LoadGame",FID="F2"});rows.Add(new{@event="PayFines",Faction="Authority",Amount=400,AllFines=true});
var path=directory.File("Journal.fines.log");await File.WriteAllLinesAsync(path,rows.Select(value=>JsonSerializer.Serialize(value)));
var before=await StationAccessWaiter.ReadKnownFinesAsync(directory.Path,"F1",default);
Check.Equal(1,before.JournalFilesRead);Check.Equal(100,before.Restrictions.Count);Check.Equal(400L,before.FineAt(1));Check.Equal(400L,before.FineAt(100));
Check.Equal("Authority",before.Restrictions[1].Authority);Check.Equal(0L,before.FineAt(999));
await File.AppendAllLinesAsync(path,new object[]{new{@event="LoadGame",FID="F1"},new{@event="PayFines",Faction="Authority",Amount=400}}.Select(value=>JsonSerializer.Serialize(value)));
var after=await StationAccessWaiter.ReadKnownFinesAsync(directory.Path,"F1",default);
Check.Equal(1,after.JournalFilesRead);Check.Equal(0,after.Restrictions.Count);Check.Equal(0L,after.FineAt(1));
Check.Equal(400L,before.FineAt(1));
}
[Test]public void UnchangedFineReportsOnceAndBacksOffWithABoundedDelayWhileChangedAmountReportsImmediately()
{
var schedule=new StationAccessWaitSchedule();var reports=0;var seconds=new List<double>();
for(var i=0;i<8;i++){var observation=schedule.Observe(400);if(observation.Report)reports++;seconds.Add(observation.Delay.TotalSeconds);}
Check.Equal(1,reports);Check.Equal("1,2,4,8,15,30,30,30",string.Join(',',seconds));
var changed=schedule.Observe(200);Check.That(changed.Report);Check.Equal(TimeSpan.FromSeconds(1),changed.Delay);
Check.That(!schedule.Observe(200).Report);
}
[Test]public async Task AWaitingTradeNeverContinuesAfterThePlayerChangesShipSessionOrDockedMarket()
{
foreach(var change in new Func<PlayerState,PlayerState>[]
{
p=>p with{CommanderId="Other"},p=>p with{CurrentShipId=8},p=>p with{LoadGameAt=p.LoadGameAt!.Value.AddSeconds(1)},
p=>p with{MarketId=3},p=>p with{Docked=false},p=>p with{InGame=false}
})
{
using var directory=new TemporaryDirectory();var at=DateTimeOffset.UtcNow.AddMinutes(-1);
var current=new PlayerState{InGame=true,Docked=true,MarketId=2,CommanderId="F1",CurrentShipId=7,LoadGameAt=at};
await File.WriteAllLinesAsync(directory.File("Journal.fine.log"),new object[]
{
new{@event="LoadGame",FID="F1"},new{@event="Docked",MarketID=2,StationFaction=new{Name="Authority"}},
new{@event="CommitCrime",Faction="Authority",Fine=400}
}.Select(value=>JsonSerializer.Serialize(value)));
using var budget=new CancellationTokenSource(TimeSpan.FromSeconds(4));var refused=false;
try{await StationAccessWaiter.WaitAsync(directory.Path,()=>current,2,_=>current=change(current),budget.Token);}
catch(Exception error)when(error is InvalidDataException or InvalidOperationException){refused=true;}
Check.That(refused,"Changing the actual ship/session/port must stop the waiting order rather than clear its fine gate.");
}
}
}
using System.Text.Json;
using XFEEDToolBox.Core.Execution;
using XFEEDToolBox.Core.Models;
using XFEExtension.NetCore.XUnit.Attributes;
namespace XFEEDToolBox.Test;
[TestFixture]
public sealed class StationAccessWaiterTests
{
[Test]public async Task OneJournalPassResolvesAllKnownMarketsForThisFidAndActualPaymentRemovesTheirFineExclusion()
{
using var directory=new TemporaryDirectory();var rows=new List<object>{new{@event="LoadGame",FID="F1"}};
for(var market=1;market<=100;market++)rows.Add(new{@event="Docked",MarketID=market,StationFaction=new{Name="Authority"}});
rows.Add(new{@event="CommitCrime",Faction="Authority",Fine=400});
rows.Add(new{@event="LoadGame",FID="F2"});rows.Add(new{@event="PayFines",Faction="Authority",Amount=400,AllFines=true});
var path=directory.File("Journal.fines.log");await File.WriteAllLinesAsync(path,rows.Select(value=>JsonSerializer.Serialize(value)));
var before=await StationAccessWaiter.ReadKnownFinesAsync(directory.Path,"F1",default);
Check.Equal(1,before.JournalFilesRead);Check.Equal(100,before.Restrictions.Count);Check.Equal(400L,before.FineAt(1));Check.Equal(400L,before.FineAt(100));
Check.Equal("Authority",before.Restrictions[1].Authority);Check.Equal(0L,before.FineAt(999));
await File.AppendAllLinesAsync(path,new object[]{new{@event="LoadGame",FID="F1"},new{@event="PayFines",Faction="Authority",Amount=400}}.Select(value=>JsonSerializer.Serialize(value)));
var after=await StationAccessWaiter.ReadKnownFinesAsync(directory.Path,"F1",default);
Check.Equal(1,after.JournalFilesRead);Check.Equal(0,after.Restrictions.Count);Check.Equal(0L,after.FineAt(1));
Check.Equal(400L,before.FineAt(1));
}
[Test]public void UnchangedFineReportsOnceAndBacksOffWithABoundedDelayWhileChangedAmountReportsImmediately()
{
var schedule=new StationAccessWaitSchedule();var reports=0;var seconds=new List<double>();
for(var i=0;i<8;i++){var observation=schedule.Observe(400);if(observation.Report)reports++;seconds.Add(observation.Delay.TotalSeconds);}
Check.Equal(1,reports);Check.Equal("1,2,4,8,15,30,30,30",string.Join(',',seconds));
var changed=schedule.Observe(200);Check.That(changed.Report);Check.Equal(TimeSpan.FromSeconds(1),changed.Delay);
Check.That(!schedule.Observe(200).Report);
}
[Test]public async Task AWaitingTradeNeverContinuesAfterThePlayerChangesShipSessionOrDockedMarket()
{
foreach(var change in new Func<PlayerState,PlayerState>[]
{
p=>p with{CommanderId="Other"},p=>p with{CurrentShipId=8},p=>p with{LoadGameAt=p.LoadGameAt!.Value.AddSeconds(1)},
p=>p with{MarketId=3},p=>p with{Docked=false},p=>p with{InGame=false}
})
{
using var directory=new TemporaryDirectory();var at=DateTimeOffset.UtcNow.AddMinutes(-1);
var current=new PlayerState{InGame=true,Docked=true,MarketId=2,CommanderId="F1",CurrentShipId=7,LoadGameAt=at};
await File.WriteAllLinesAsync(directory.File("Journal.fine.log"),new object[]
{
new{@event="LoadGame",FID="F1"},new{@event="Docked",MarketID=2,StationFaction=new{Name="Authority"}},
new{@event="CommitCrime",Faction="Authority",Fine=400}
}.Select(value=>JsonSerializer.Serialize(value)));
using var budget=new CancellationTokenSource(TimeSpan.FromSeconds(4));var refused=false;
try{await StationAccessWaiter.WaitAsync(directory.Path,()=>current,2,_=>current=change(current),budget.Token);}
catch(Exception error)when(error is InvalidDataException or InvalidOperationException){refused=true;}
Check.That(refused,"Changing the actual ship/session/port must stop the waiting order rather than clear its fine gate.");
}
}
}