using XFEExtension.NetCore.XUnit.Attributes;
using XFEEDToolBox.Core.Data;
using XFEEDToolBox.Core.Models;
namespace XFEEDToolBox.Test;
[TestFixture]
public class CacheRegionTests
{
private static async Task<MarketRepository> Seed(TemporaryDirectory directory,params Station[] stations)
{
var repository=new MarketRepository(directory.File("markets.db"));repository.InitializeCheap();var at=DateTimeOffset.UtcNow.AddMinutes(-1);
foreach(var station in stations)
await repository.SaveSnapshotAsync(new(station,[Check.Quote(station.MarketId,"commodity"+station.MarketId,10,9)],at,DataSource.Journal));
return repository;
}
private static string Ids(IEnumerable<Station> stations)=>string.Join(',',stations.Select(station=>station.MarketId));
[Test]public async Task NearbyStationsUseAnExactSphereAndDeterministicDistanceOrder()
{
using var directory=new TemporaryDirectory();var center=new Coordinates(10,20,30);
var repository=await Seed(directory,
Check.Station(1) with{Position=center},
Check.Station(2) with{Position=new(13,24,30)},
Check.Station(3) with{Position=new(14,24,30)},
Check.Station(4) with{Position=new(8,20,30),SystemName="A"},
Check.Station(5) with{Position=null},
Check.Station(6) with{Position=new(12,20,30),SystemName="Z"});
Check.Equal("1,4,6,2",Ids(repository.GetStationsNear(center,5)));
Check.Equal("1,4",Ids(repository.GetStationsNear(center,5,limit:2)));
Check.Equal(new Coordinates(13,24,30),repository.GetStationsNear(center,5).Last().Position!);
}
[Test]public async Task ExplicitCenterFiltersTheBookWhileKeepingTheActualOriginFirst()
{
using var directory=new TemporaryDirectory();var repository=await Seed(directory,Check.Station(1,15),Check.Station(2,0),Check.Station(3,-5),Check.Station(4,30));
var book=repository.LoadRegion(1,20,center:new(0,0,0));
Check.Equal("1,2,3",Ids(book.Stations));Check.Equal(new Coordinates(15,0,0),book.Stations[0].Position!);
Check.Equal("1,2,3",string.Join(',',book.Quotes.Select(quote=>quote.MarketId).Order()));Check.That(!book.Truncated);
Check.Equal("2,3,1",Ids(repository.GetStationsNear(new(0,0,0),20)));
}
[Test]public async Task OmittingTheCenterPreservesTheOriginCenteredRegion()
{
using var directory=new TemporaryDirectory();var repository=await Seed(directory,Check.Station(1,1000),Check.Station(2,1010),Check.Station(3,0));
var book=repository.LoadRegion(1,25);Check.Equal("1,2",Ids(book.Stations));Check.Equal(2,book.Quotes.Count);
}
[Test]public async Task AnOriginOutsideTheChosenCenterIsExplicitlyRejected()
{
using var directory=new TemporaryDirectory();var repository=await Seed(directory,Check.Station(1,21),Check.Station(2,0));
Check.Throws<InvalidOperationException>(()=>repository.LoadRegion(1,20,center:new(0,0,0)));
// The actual route origin is never silently replaced by the center.
Check.Equal(new Coordinates(21,0,0),repository.GetStation(1)!.Position!);
}
[Test]public async Task KnownCenterDoesNotInventAnUnknownOriginPosition()
{
using var directory=new TemporaryDirectory();var repository=await Seed(directory,Check.Station(1) with{Position=null},Check.Station(2,0));
Check.Throws<ArgumentException>(()=>repository.LoadRegion(1,500,center:new(0,0,0)));
Check.Throws<ArgumentException>(()=>repository.LoadRegion(999,500,center:new(0,0,0)));
}
[Test]public async Task RadiusAndCenterMustBeFiniteAndPositive()
{
using var directory=new TemporaryDirectory();var repository=await Seed(directory,Check.Station(1,0));var center=new Coordinates(0,0,0);
foreach(var radius in new[]{0d,-1d,double.NaN,double.PositiveInfinity,double.NegativeInfinity,double.MaxValue})
{
Check.Throws<ArgumentOutOfRangeException>(()=>repository.GetStationsNear(center,radius));
Check.Throws<ArgumentOutOfRangeException>(()=>repository.LoadRegion(1,radius,center:center));
}
Check.Throws<ArgumentException>(()=>repository.GetStationsNear(new(double.NaN,0,0),500));
Check.Throws<ArgumentException>(()=>repository.LoadRegion(1,500,center:new(0,double.PositiveInfinity,0)));
}
[Test]public async Task TruncatedBooksIncludeOnlySelectedStationsAndTheirQuotes()
{
using var directory=new TemporaryDirectory();var repository=await Seed(directory,Enumerable.Range(1,5).Select(id=>Check.Station(id,id-1)).ToArray());
var book=repository.LoadRegion(5,10,limit:2,center:new(0,0,0));
Check.That(book.Truncated);Check.Equal("5,1",Ids(book.Stations));Check.Equal(2,book.Quotes.Count);
Check.That(book.Quotes.All(quote=>quote.MarketId is 5 or 1));
var full=repository.LoadRegion(5,10,limit:5,center:new(0,0,0));Check.That(!full.Truncated);Check.Equal(5,full.Quotes.Count);
}
}
using XFEExtension.NetCore.XUnit.Attributes;
using XFEEDToolBox.Core.Data;
using XFEEDToolBox.Core.Models;
namespace XFEEDToolBox.Test;
[TestFixture]
public class CacheRegionTests
{
private static async Task<MarketRepository> Seed(TemporaryDirectory directory,params Station[] stations)
{
var repository=new MarketRepository(directory.File("markets.db"));repository.InitializeCheap();var at=DateTimeOffset.UtcNow.AddMinutes(-1);
foreach(var station in stations)
await repository.SaveSnapshotAsync(new(station,[Check.Quote(station.MarketId,"commodity"+station.MarketId,10,9)],at,DataSource.Journal));
return repository;
}
private static string Ids(IEnumerable<Station> stations)=>string.Join(',',stations.Select(station=>station.MarketId));
[Test]public async Task NearbyStationsUseAnExactSphereAndDeterministicDistanceOrder()
{
using var directory=new TemporaryDirectory();var center=new Coordinates(10,20,30);
var repository=await Seed(directory,
Check.Station(1) with{Position=center},
Check.Station(2) with{Position=new(13,24,30)},
Check.Station(3) with{Position=new(14,24,30)},
Check.Station(4) with{Position=new(8,20,30),SystemName="A"},
Check.Station(5) with{Position=null},
Check.Station(6) with{Position=new(12,20,30),SystemName="Z"});
Check.Equal("1,4,6,2",Ids(repository.GetStationsNear(center,5)));
Check.Equal("1,4",Ids(repository.GetStationsNear(center,5,limit:2)));
Check.Equal(new Coordinates(13,24,30),repository.GetStationsNear(center,5).Last().Position!);
}
[Test]public async Task ExplicitCenterFiltersTheBookWhileKeepingTheActualOriginFirst()
{
using var directory=new TemporaryDirectory();var repository=await Seed(directory,Check.Station(1,15),Check.Station(2,0),Check.Station(3,-5),Check.Station(4,30));
var book=repository.LoadRegion(1,20,center:new(0,0,0));
Check.Equal("1,2,3",Ids(book.Stations));Check.Equal(new Coordinates(15,0,0),book.Stations[0].Position!);
Check.Equal("1,2,3",string.Join(',',book.Quotes.Select(quote=>quote.MarketId).Order()));Check.That(!book.Truncated);
Check.Equal("2,3,1",Ids(repository.GetStationsNear(new(0,0,0),20)));
}
[Test]public async Task OmittingTheCenterPreservesTheOriginCenteredRegion()
{
using var directory=new TemporaryDirectory();var repository=await Seed(directory,Check.Station(1,1000),Check.Station(2,1010),Check.Station(3,0));
var book=repository.LoadRegion(1,25);Check.Equal("1,2",Ids(book.Stations));Check.Equal(2,book.Quotes.Count);
}
[Test]public async Task AnOriginOutsideTheChosenCenterIsExplicitlyRejected()
{
using var directory=new TemporaryDirectory();var repository=await Seed(directory,Check.Station(1,21),Check.Station(2,0));
Check.Throws<InvalidOperationException>(()=>repository.LoadRegion(1,20,center:new(0,0,0)));
// The actual route origin is never silently replaced by the center.
Check.Equal(new Coordinates(21,0,0),repository.GetStation(1)!.Position!);
}
[Test]public async Task KnownCenterDoesNotInventAnUnknownOriginPosition()
{
using var directory=new TemporaryDirectory();var repository=await Seed(directory,Check.Station(1) with{Position=null},Check.Station(2,0));
Check.Throws<ArgumentException>(()=>repository.LoadRegion(1,500,center:new(0,0,0)));
Check.Throws<ArgumentException>(()=>repository.LoadRegion(999,500,center:new(0,0,0)));
}
[Test]public async Task RadiusAndCenterMustBeFiniteAndPositive()
{
using var directory=new TemporaryDirectory();var repository=await Seed(directory,Check.Station(1,0));var center=new Coordinates(0,0,0);
foreach(var radius in new[]{0d,-1d,double.NaN,double.PositiveInfinity,double.NegativeInfinity,double.MaxValue})
{
Check.Throws<ArgumentOutOfRangeException>(()=>repository.GetStationsNear(center,radius));
Check.Throws<ArgumentOutOfRangeException>(()=>repository.LoadRegion(1,radius,center:center));
}
Check.Throws<ArgumentException>(()=>repository.GetStationsNear(new(double.NaN,0,0),500));
Check.Throws<ArgumentException>(()=>repository.LoadRegion(1,500,center:new(0,double.PositiveInfinity,0)));
}
[Test]public async Task TruncatedBooksIncludeOnlySelectedStationsAndTheirQuotes()
{
using var directory=new TemporaryDirectory();var repository=await Seed(directory,Enumerable.Range(1,5).Select(id=>Check.Station(id,id-1)).ToArray());
var book=repository.LoadRegion(5,10,limit:2,center:new(0,0,0));
Check.That(book.Truncated);Check.Equal("5,1",Ids(book.Stations));Check.Equal(2,book.Quotes.Count);
Check.That(book.Quotes.All(quote=>quote.MarketId is 5 or 1));
var full=repository.LoadRegion(5,10,limit:5,center:new(0,0,0));Check.That(!full.Truncated);Check.Equal(5,full.Quotes.Count);
}
}