using Microsoft.Data.Sqlite;
using XFEExtension.NetCore.XUnit.Attributes;
using XFEEDToolBox.Core.Data;
using XFEEDToolBox.Core.Models;
namespace XFEEDToolBox.Test;
[TestFixture]
public class MarketSearchTests
{
[Test]public async Task OptimizedQueriesPreserveDisplayAliasesLocationsSortingAndPages()
{
using var temporary=new TemporaryDirectory();var repository=temporary.Repository();var at=DateTimeOffset.UtcNow.AddMinutes(-1);
var stations=new[]{Check.Station(1) with{SystemName="Alpha",Name="Homer Platform"},Check.Station(2) with{SystemName="Beta",Name="Watershed"},Check.Station(3) with{SystemName="Gamma",Name="Custom Port"}};
foreach(var station in stations)
{
var quotes=new[]{Check.Quote(station.MarketId,"water",30+station.MarketId,20) with{DisplayName="Collector Gold Leaf",Supply=10+station.MarketId,Demand=40-station.MarketId},
Check.Quote(station.MarketId,"gold",90-station.MarketId,70) with{DisplayName="Royal Auric",Supply=30-station.MarketId,Demand=10+station.MarketId},
Check.Quote(station.MarketId,"customsymbol",40+station.MarketId,30) with{DisplayName="Uncatalogued Water Alias",Supply=5,Demand=9}};
await repository.SaveSnapshotAsync(new(station,quotes,at.AddSeconds(station.MarketId),DataSource.Journal));
}
using var connection=new SqliteConnection(new SqliteConnectionStringBuilder{DataSource=repository.DatabasePath,Mode=SqliteOpenMode.ReadOnly}.ToString());connection.Open();
foreach(var term in new[]{"gold","WATER","Homer","Gamma","custom","not-found","%","_","' OR 1=1 --"})
foreach(var sort in Enum.GetValues<MarketSort>())foreach(long? station in new long?[]{null,1})
{
using var query=connection.CreateCommand();
var order=sort switch{MarketSort.Newest=>"q.timestamp DESC",MarketSort.BuyDescending=>"q.buy DESC",MarketSort.SellDescending=>"q.sell DESC",MarketSort.SupplyDescending=>"q.supply DESC",MarketSort.DemandDescending=>"q.demand DESC",_=>"s.system,s.name,q.commodity"};
query.CommandText=$"SELECT s.id,q.commodity FROM quotes q JOIN stations s ON s.id=q.station WHERE ($id IS NULL OR s.id=$id) AND(q.name LIKE $q OR q.commodity LIKE $q OR s.name LIKE $q OR s.system LIKE $q) ORDER BY {order},s.system,s.name,q.commodity";
query.Parameters.AddWithValue("$id",(object?)station??DBNull.Value);query.Parameters.AddWithValue("$q","%"+term.Replace("%","").Replace("_","")+"%");
var expected=new List<string>();using(var reader=query.ExecuteReader())while(reader.Read())expected.Add(reader.GetInt64(0)+":"+reader.GetString(1));
foreach(var offset in new[]{0,1,4})
{
var actual=repository.Search(term,station,offset,2,sort).Select(row=>row.Station.MarketId+":"+row.Quote.Commodity);
Check.Equal(string.Join('|',expected.Skip(offset).Take(2)),string.Join('|',actual));
}
}
}
[Test]public void PreCancelledSearchDoesNotOpenOrCreateADatabase()
{
using var temporary=new TemporaryDirectory();var path=Path.Combine(temporary.Path,"not-created.db");
using var cancellation=new CancellationTokenSource();cancellation.Cancel();
Check.Throws<OperationCanceledException>(()=>new MarketRepository(path).Search("water",null,token:cancellation.Token));
Check.That(!File.Exists(path));
}
[Test]public void SearchOpensOnlyAnExistingReadOnlyDatabase()
{
using var temporary=new TemporaryDirectory();var path=Path.Combine(temporary.Path,"not-created.db");
Check.Throws<SqliteException>(()=>new MarketRepository(path).Search("water",null));Check.That(!File.Exists(path));
}
[Test]public void NonpositiveSearchBudgetIsRejectedBeforeReading()
{
using var temporary=new TemporaryDirectory();var path=Path.Combine(temporary.Path,"not-created.db");
Check.Throws<ArgumentOutOfRangeException>(()=>new MarketRepository(path).Search("water",null,budget:TimeSpan.Zero));Check.That(!File.Exists(path));
}
}
using Microsoft.Data.Sqlite;
using XFEExtension.NetCore.XUnit.Attributes;
using XFEEDToolBox.Core.Data;
using XFEEDToolBox.Core.Models;
namespace XFEEDToolBox.Test;
[TestFixture]
public class MarketSearchTests
{
[Test]public async Task OptimizedQueriesPreserveDisplayAliasesLocationsSortingAndPages()
{
using var temporary=new TemporaryDirectory();var repository=temporary.Repository();var at=DateTimeOffset.UtcNow.AddMinutes(-1);
var stations=new[]{Check.Station(1) with{SystemName="Alpha",Name="Homer Platform"},Check.Station(2) with{SystemName="Beta",Name="Watershed"},Check.Station(3) with{SystemName="Gamma",Name="Custom Port"}};
foreach(var station in stations)
{
var quotes=new[]{Check.Quote(station.MarketId,"water",30+station.MarketId,20) with{DisplayName="Collector Gold Leaf",Supply=10+station.MarketId,Demand=40-station.MarketId},
Check.Quote(station.MarketId,"gold",90-station.MarketId,70) with{DisplayName="Royal Auric",Supply=30-station.MarketId,Demand=10+station.MarketId},
Check.Quote(station.MarketId,"customsymbol",40+station.MarketId,30) with{DisplayName="Uncatalogued Water Alias",Supply=5,Demand=9}};
await repository.SaveSnapshotAsync(new(station,quotes,at.AddSeconds(station.MarketId),DataSource.Journal));
}
using var connection=new SqliteConnection(new SqliteConnectionStringBuilder{DataSource=repository.DatabasePath,Mode=SqliteOpenMode.ReadOnly}.ToString());connection.Open();
foreach(var term in new[]{"gold","WATER","Homer","Gamma","custom","not-found","%","_","' OR 1=1 --"})
foreach(var sort in Enum.GetValues<MarketSort>())foreach(long? station in new long?[]{null,1})
{
using var query=connection.CreateCommand();
var order=sort switch{MarketSort.Newest=>"q.timestamp DESC",MarketSort.BuyDescending=>"q.buy DESC",MarketSort.SellDescending=>"q.sell DESC",MarketSort.SupplyDescending=>"q.supply DESC",MarketSort.DemandDescending=>"q.demand DESC",_=>"s.system,s.name,q.commodity"};
query.CommandText=$"SELECT s.id,q.commodity FROM quotes q JOIN stations s ON s.id=q.station WHERE ($id IS NULL OR s.id=$id) AND(q.name LIKE $q OR q.commodity LIKE $q OR s.name LIKE $q OR s.system LIKE $q) ORDER BY {order},s.system,s.name,q.commodity";
query.Parameters.AddWithValue("$id",(object?)station??DBNull.Value);query.Parameters.AddWithValue("$q","%"+term.Replace("%","").Replace("_","")+"%");
var expected=new List<string>();using(var reader=query.ExecuteReader())while(reader.Read())expected.Add(reader.GetInt64(0)+":"+reader.GetString(1));
foreach(var offset in new[]{0,1,4})
{
var actual=repository.Search(term,station,offset,2,sort).Select(row=>row.Station.MarketId+":"+row.Quote.Commodity);
Check.Equal(string.Join('|',expected.Skip(offset).Take(2)),string.Join('|',actual));
}
}
}
[Test]public void PreCancelledSearchDoesNotOpenOrCreateADatabase()
{
using var temporary=new TemporaryDirectory();var path=Path.Combine(temporary.Path,"not-created.db");
using var cancellation=new CancellationTokenSource();cancellation.Cancel();
Check.Throws<OperationCanceledException>(()=>new MarketRepository(path).Search("water",null,token:cancellation.Token));
Check.That(!File.Exists(path));
}
[Test]public void SearchOpensOnlyAnExistingReadOnlyDatabase()
{
using var temporary=new TemporaryDirectory();var path=Path.Combine(temporary.Path,"not-created.db");
Check.Throws<SqliteException>(()=>new MarketRepository(path).Search("water",null));Check.That(!File.Exists(path));
}
[Test]public void NonpositiveSearchBudgetIsRejectedBeforeReading()
{
using var temporary=new TemporaryDirectory();var path=Path.Combine(temporary.Path,"not-created.db");
Check.Throws<ArgumentOutOfRangeException>(()=>new MarketRepository(path).Search("water",null,budget:TimeSpan.Zero));Check.That(!File.Exists(path));
}
}