using System.Text.Json;
using XFEExtension.NetCore.XUnit.Attributes;
using XFEEDToolBox.Core.Data;
using XFEEDToolBox.Core.Models;
using XFEEDToolBox.Core.Services;
namespace XFEEDToolBox.Test;
[TestFixture]
public class NearbyMarketCacheTests
{
[Test]public void RadiusAndUnknownPositionsDoNotAdmitTheGalaxy()
{
var player=new PlayerState{SystemName="Home",MarketId=1,Position=new(0,0,0)};
Check.That(NearbyMarketCachePolicy.Accepts(player,Check.Station(2,500)));
Check.That(!NearbyMarketCachePolicy.Accepts(player,Check.Station(2,500.001)));
Check.That(!NearbyMarketCachePolicy.Accepts(player,Check.Station(2) with{Position=null}));
Check.That(NearbyMarketCachePolicy.Accepts(player,Check.Station(2) with{Position=null},Check.Station(2,149)));
Check.That(!NearbyMarketCachePolicy.Accepts(player,Check.Station(2) with{Position=null,SystemName="Moved carrier"},Check.Station(2,1)));
Check.That(!NearbyMarketCachePolicy.Accepts(player with{Position=null},Check.Station(2,1)));
Check.That(NearbyMarketCachePolicy.Accepts(player with{Position=null},Check.Station(2) with{SystemName="home",Position=null}));
Check.That(NearbyMarketCachePolicy.Accepts(player with{Position=null},Check.Station(1) with{Position=null}));
Check.That(!NearbyMarketCachePolicy.Accepts(new(),Check.Station(2,0)));
Check.That(!NearbyMarketCachePolicy.Accepts(player,Check.Station(2) with{Position=new(double.NaN,0,0)}));
}
private static JsonElement Eddn(long id,string system,DateTimeOffset time)=>Check.Event(new
{
// Attribute names on the wire include a dollar sign.
header=new{gameversion="4.0"},message=new{timestamp=time,marketId=id,systemName=system,stationName="Station "+id,
commodities=new[]{new{name="water",buyPrice=10,sellPrice=9,stock=100,demand=100}}}
});
private static JsonElement Message(long id,string system,DateTimeOffset time)
{
using var document=JsonDocument.Parse(Eddn(id,system,time).GetRawText());
var values=new Dictionary<string,object>{{"$schemaRef","https://eddn.edcd.io/schemas/commodity/3"},
{"header",document.RootElement.GetProperty("header").Clone()},{"message",document.RootElement.GetProperty("message").Clone()}};
return JsonSerializer.SerializeToElement(values);
}
[Test]public async Task RealFeedPathKeepsOnlyCurrentOrLocatedNearbyDataAndAlwaysKeepsJournal()
{
using var temporary=new TemporaryDirectory();var repository=new MarketRepository(temporary.File("markets.db"));repository.InitializeCheap();
await repository.SaveStationAsync(Check.Station(2,149),localObservation:true);
await repository.SaveStationAsync(Check.Station(3,501),localObservation:true);
var player=new PlayerState{SystemName="Home",Position=new(0,0,0),MarketId=1};
repository.PublicDataFilter=station=>NearbyMarketCachePolicy.Accepts(player,station,repository.GetStation(station.MarketId));
await using var feed=new EddnSubscriber(repository);var at=DateTimeOffset.UtcNow.AddMinutes(-1);
Check.That(await feed.ProcessMessageAsync(Message(2,"System 2",at)));
Check.That(!await feed.ProcessMessageAsync(Message(3,"System 3",at)));
Check.That(!await feed.ProcessMessageAsync(Message(4,"Unknown position",at)));
Check.That(await feed.ProcessMessageAsync(Message(5,"Home",at)));
await feed.ProcessMessageAsync(JsonSerializer.SerializeToElement(new Dictionary<string,object>
{{"$schemaRef","https://eddn.edcd.io/schemas/journal/1"},{"header",new{gameversion="4.0"}},
{"message",new{StarSystem="New nearby system",StarPos=new[]{100,0,0}}}}));
Check.That(await feed.ProcessMessageAsync(Message(6,"New nearby system",at)));
Check.That(await repository.SaveSnapshotAsync(new(Check.Station(9,5000),[Check.Quote(9,"food",20,19)],at,DataSource.Journal)));
Check.Equal(4L,repository.Statistics().Quotes);Check.Equal(3L,feed.MarketsAccepted);
Check.That(repository.GetStation(4) is null);Check.That(repository.GetStation(9) is not null);
player=player with{Position=null,SystemName="Home"};
Check.That(!await feed.ProcessMessageAsync(Message(2,"System 2",at.AddSeconds(1))));
Check.That(await feed.ProcessMessageAsync(Message(5,"Home",at.AddSeconds(1))));
}
[Test]public async Task NormalRuntimeLeavesAnInvalidLegacyReceiptForExplicitMaintenanceWithoutScanning()
{
using var temporary=new TemporaryDirectory();var seed=temporary.Repository();
await seed.SaveSnapshotAsync(new(Check.Station(1),[Check.Quote(1,"water",10,9)],DateTimeOffset.UtcNow,DataSource.Journal));
var proof=seed.DatabasePath+".verified-generation.json";File.WriteAllText(proof,"{\"old\":true}");
var diagnostic=seed.DatabasePath+".diagnostics.jsonl";var before=File.Exists(diagnostic)?File.ReadAllText(diagnostic):"";
var backupTime=File.GetLastWriteTimeUtc(seed.VerifiedBackupPath);
await using(var runtime=new ToolboxRuntime(temporary.Path))
{
await runtime.InitializeAsync(offline:true);
await runtime.Markets.SaveSnapshotAsync(new(Check.Station(1),[Check.Quote(1,"water",11,10)],DateTimeOffset.UtcNow,DataSource.Journal));
var additional=File.ReadAllText(diagnostic)[before.Length..];
Check.That(!additional.Contains("full-integrity"));Check.That(!additional.Contains("verified-generation-rebuilt"));
Check.Equal("{\"old\":true}",File.ReadAllText(proof));
Check.Equal(backupTime,File.GetLastWriteTimeUtc(seed.VerifiedBackupPath));
runtime.ClearMarketCacheBlockReason=()=>"请先停止当前任务。";
var refusal=await runtime.ClearMarketCacheAsync();Check.That(!refusal.Succeeded);Check.Equal("请先停止当前任务。",refusal.Message);
Check.Equal(1L,runtime.Markets.Statistics().Quotes);
}
Check.Equal("{\"old\":true}",File.ReadAllText(proof));
}
}
using System.Text.Json;
using XFEExtension.NetCore.XUnit.Attributes;
using XFEEDToolBox.Core.Data;
using XFEEDToolBox.Core.Models;
using XFEEDToolBox.Core.Services;
namespace XFEEDToolBox.Test;
[TestFixture]
public class NearbyMarketCacheTests
{
[Test]public void RadiusAndUnknownPositionsDoNotAdmitTheGalaxy()
{
var player=new PlayerState{SystemName="Home",MarketId=1,Position=new(0,0,0)};
Check.That(NearbyMarketCachePolicy.Accepts(player,Check.Station(2,500)));
Check.That(!NearbyMarketCachePolicy.Accepts(player,Check.Station(2,500.001)));
Check.That(!NearbyMarketCachePolicy.Accepts(player,Check.Station(2) with{Position=null}));
Check.That(NearbyMarketCachePolicy.Accepts(player,Check.Station(2) with{Position=null},Check.Station(2,149)));
Check.That(!NearbyMarketCachePolicy.Accepts(player,Check.Station(2) with{Position=null,SystemName="Moved carrier"},Check.Station(2,1)));
Check.That(!NearbyMarketCachePolicy.Accepts(player with{Position=null},Check.Station(2,1)));
Check.That(NearbyMarketCachePolicy.Accepts(player with{Position=null},Check.Station(2) with{SystemName="home",Position=null}));
Check.That(NearbyMarketCachePolicy.Accepts(player with{Position=null},Check.Station(1) with{Position=null}));
Check.That(!NearbyMarketCachePolicy.Accepts(new(),Check.Station(2,0)));
Check.That(!NearbyMarketCachePolicy.Accepts(player,Check.Station(2) with{Position=new(double.NaN,0,0)}));
}
private static JsonElement Eddn(long id,string system,DateTimeOffset time)=>Check.Event(new
{
// Attribute names on the wire include a dollar sign.
header=new{gameversion="4.0"},message=new{timestamp=time,marketId=id,systemName=system,stationName="Station "+id,
commodities=new[]{new{name="water",buyPrice=10,sellPrice=9,stock=100,demand=100}}}
});
private static JsonElement Message(long id,string system,DateTimeOffset time)
{
using var document=JsonDocument.Parse(Eddn(id,system,time).GetRawText());
var values=new Dictionary<string,object>{{"$schemaRef","https://eddn.edcd.io/schemas/commodity/3"},
{"header",document.RootElement.GetProperty("header").Clone()},{"message",document.RootElement.GetProperty("message").Clone()}};
return JsonSerializer.SerializeToElement(values);
}
[Test]public async Task RealFeedPathKeepsOnlyCurrentOrLocatedNearbyDataAndAlwaysKeepsJournal()
{
using var temporary=new TemporaryDirectory();var repository=new MarketRepository(temporary.File("markets.db"));repository.InitializeCheap();
await repository.SaveStationAsync(Check.Station(2,149),localObservation:true);
await repository.SaveStationAsync(Check.Station(3,501),localObservation:true);
var player=new PlayerState{SystemName="Home",Position=new(0,0,0),MarketId=1};
repository.PublicDataFilter=station=>NearbyMarketCachePolicy.Accepts(player,station,repository.GetStation(station.MarketId));
await using var feed=new EddnSubscriber(repository);var at=DateTimeOffset.UtcNow.AddMinutes(-1);
Check.That(await feed.ProcessMessageAsync(Message(2,"System 2",at)));
Check.That(!await feed.ProcessMessageAsync(Message(3,"System 3",at)));
Check.That(!await feed.ProcessMessageAsync(Message(4,"Unknown position",at)));
Check.That(await feed.ProcessMessageAsync(Message(5,"Home",at)));
await feed.ProcessMessageAsync(JsonSerializer.SerializeToElement(new Dictionary<string,object>
{{"$schemaRef","https://eddn.edcd.io/schemas/journal/1"},{"header",new{gameversion="4.0"}},
{"message",new{StarSystem="New nearby system",StarPos=new[]{100,0,0}}}}));
Check.That(await feed.ProcessMessageAsync(Message(6,"New nearby system",at)));
Check.That(await repository.SaveSnapshotAsync(new(Check.Station(9,5000),[Check.Quote(9,"food",20,19)],at,DataSource.Journal)));
Check.Equal(4L,repository.Statistics().Quotes);Check.Equal(3L,feed.MarketsAccepted);
Check.That(repository.GetStation(4) is null);Check.That(repository.GetStation(9) is not null);
player=player with{Position=null,SystemName="Home"};
Check.That(!await feed.ProcessMessageAsync(Message(2,"System 2",at.AddSeconds(1))));
Check.That(await feed.ProcessMessageAsync(Message(5,"Home",at.AddSeconds(1))));
}
[Test]public async Task NormalRuntimeLeavesAnInvalidLegacyReceiptForExplicitMaintenanceWithoutScanning()
{
using var temporary=new TemporaryDirectory();var seed=temporary.Repository();
await seed.SaveSnapshotAsync(new(Check.Station(1),[Check.Quote(1,"water",10,9)],DateTimeOffset.UtcNow,DataSource.Journal));
var proof=seed.DatabasePath+".verified-generation.json";File.WriteAllText(proof,"{\"old\":true}");
var diagnostic=seed.DatabasePath+".diagnostics.jsonl";var before=File.Exists(diagnostic)?File.ReadAllText(diagnostic):"";
var backupTime=File.GetLastWriteTimeUtc(seed.VerifiedBackupPath);
await using(var runtime=new ToolboxRuntime(temporary.Path))
{
await runtime.InitializeAsync(offline:true);
await runtime.Markets.SaveSnapshotAsync(new(Check.Station(1),[Check.Quote(1,"water",11,10)],DateTimeOffset.UtcNow,DataSource.Journal));
var additional=File.ReadAllText(diagnostic)[before.Length..];
Check.That(!additional.Contains("full-integrity"));Check.That(!additional.Contains("verified-generation-rebuilt"));
Check.Equal("{\"old\":true}",File.ReadAllText(proof));
Check.Equal(backupTime,File.GetLastWriteTimeUtc(seed.VerifiedBackupPath));
runtime.ClearMarketCacheBlockReason=()=>"请先停止当前任务。";
var refusal=await runtime.ClearMarketCacheAsync();Check.That(!refusal.Succeeded);Check.Equal("请先停止当前任务。",refusal.Message);
Check.Equal(1L,runtime.Markets.Statistics().Quotes);
}
Check.Equal("{\"old\":true}",File.ReadAllText(proof));
}
}