Add Ehwrj clean-room live map
Some checks failed
build / build-test-publish (push) Has been cancelled
Some checks failed
build / build-test-publish (push) Has been cancelled
This commit is contained in:
322
tests/Ehwrj.Tests/Program.cs
Normal file
322
tests/Ehwrj.Tests/Program.cs
Normal file
@@ -0,0 +1,322 @@
|
||||
using Ehwrj.App.Models;
|
||||
using Ehwrj.Core.Geometry;
|
||||
using Ehwrj.Core.Models;
|
||||
using Ehwrj.Core.Services;
|
||||
|
||||
namespace Ehwrj.Tests;
|
||||
|
||||
internal static class Program
|
||||
{
|
||||
private static int Main()
|
||||
{
|
||||
try
|
||||
{
|
||||
ParsesMapInfoGridSize();
|
||||
ParsesMapInfoBoundsAndStringNumbers();
|
||||
ParsesObjectsAndClassifiesAircraft();
|
||||
ParsesObjectCoordinateAndDirectionArrays();
|
||||
ClassifiesTeamsAndObjectives();
|
||||
ParsesHeadingInDegrees();
|
||||
ParsesFlightState();
|
||||
ParsesBattleMessages();
|
||||
AnalyzesCaptureFixtureQuality();
|
||||
EnforcesLoopbackNetworkScope();
|
||||
LocalizesUiTextAndStatus();
|
||||
ParsesWrappedObjectList();
|
||||
ProjectsNormalizedCoordinates();
|
||||
ProjectsBoundedCoordinates();
|
||||
Console.WriteLine("All tests passed.");
|
||||
return 0;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.Error.WriteLine(ex.Message);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
private static void ParsesMapInfoGridSize()
|
||||
{
|
||||
var info = MapInfo.FromJson("""{"grid_size":[10,20],"grid_steps":[1,2]}""");
|
||||
AssertEqual(0, info.MinX, "MinX");
|
||||
AssertEqual(10, info.MaxX, "MaxX");
|
||||
AssertEqual(0, info.MinY, "MinY");
|
||||
AssertEqual(20, info.MaxY, "MaxY");
|
||||
AssertEqual(1, info.GridStepX, "GridStepX");
|
||||
AssertEqual(2, info.GridStepY, "GridStepY");
|
||||
}
|
||||
|
||||
private static void ParsesMapInfoBoundsAndStringNumbers()
|
||||
{
|
||||
var info = MapInfo.FromJson(
|
||||
"""
|
||||
{
|
||||
"bounds": [["-1,5","-2.25"],["3,5","4.25"]],
|
||||
"grid_steps": ["0,5","1.25"]
|
||||
}
|
||||
""");
|
||||
|
||||
AssertEqual(-1.5, info.MinX, "bounds MinX");
|
||||
AssertEqual(3.5, info.MaxX, "bounds MaxX");
|
||||
AssertEqual(-2.25, info.MinY, "bounds MinY");
|
||||
AssertEqual(4.25, info.MaxY, "bounds MaxY");
|
||||
AssertEqual(0.5, info.GridStepX, "string GridStepX");
|
||||
AssertEqual(1.25, info.GridStepY, "string GridStepY");
|
||||
}
|
||||
|
||||
private static void ParsesObjectsAndClassifiesAircraft()
|
||||
{
|
||||
var tracker = new ObjectTracker();
|
||||
var objects = MapObject.FromJson(
|
||||
"""
|
||||
[
|
||||
{"id":"self","type":"player_aircraft","x":0.5,"y":0.5},
|
||||
{"id":"bandit","type":"aircraft","x":0.6,"y":0.6},
|
||||
{"id":"zone","type":"capture_zone","x":0.2,"y":0.3}
|
||||
]
|
||||
""",
|
||||
tracker);
|
||||
|
||||
AssertEqual(3, objects.Count, "object count");
|
||||
AssertTrue(objects[0].IsPlayer, "player classification");
|
||||
AssertTrue(objects[1].IsAircraft, "aircraft classification");
|
||||
AssertTrue(!objects[2].IsAircraft, "zone classification");
|
||||
}
|
||||
|
||||
private static void ParsesObjectCoordinateAndDirectionArrays()
|
||||
{
|
||||
var tracker = new ObjectTracker();
|
||||
var objects = MapObject.FromJson(
|
||||
"""
|
||||
[
|
||||
{
|
||||
"id":"array-bandit",
|
||||
"icon":"fighter",
|
||||
"team":"enemy",
|
||||
"pos":["0,25","0.75"],
|
||||
"direction":["1","0"],
|
||||
"heading":"180"
|
||||
}
|
||||
]
|
||||
""",
|
||||
tracker);
|
||||
|
||||
AssertEqual(0.25, objects[0].X, "array x");
|
||||
AssertEqual(0.75, objects[0].Y, "array y");
|
||||
AssertEqual(1, objects[0].DirectionX, "array direction x");
|
||||
AssertEqual(0, objects[0].DirectionY, "array direction y");
|
||||
AssertEqual(Math.PI, objects[0].HeadingRadians.GetValueOrDefault(), "string heading radians");
|
||||
AssertEqual((int)MapObjectKind.Enemy, (int)objects[0].Kind, "array enemy kind");
|
||||
}
|
||||
|
||||
private static void ClassifiesTeamsAndObjectives()
|
||||
{
|
||||
var tracker = new ObjectTracker();
|
||||
var objects = MapObject.FromJson(
|
||||
"""
|
||||
[
|
||||
{"id":"ally","type":"aircraft","team":"ally","x":0.1,"y":0.2},
|
||||
{"id":"enemy","type":"aircraft","team":"enemy","x":0.2,"y":0.3},
|
||||
{"id":"squad","type":"aircraft","team":"squad","x":0.3,"y":0.4},
|
||||
{"id":"target","type":"bombing_point","team":"enemy","x":0.4,"y":0.5}
|
||||
]
|
||||
""",
|
||||
tracker);
|
||||
|
||||
AssertEqual((int)MapObjectKind.Ally, (int)objects[0].Kind, "ally kind");
|
||||
AssertEqual((int)MapObjectKind.Enemy, (int)objects[1].Kind, "enemy kind");
|
||||
AssertEqual((int)MapObjectKind.Squad, (int)objects[2].Kind, "squad kind");
|
||||
AssertTrue(objects[3].IsEnemyBombingPoint, "enemy bombing point");
|
||||
}
|
||||
|
||||
private static void ParsesHeadingInDegrees()
|
||||
{
|
||||
var tracker = new ObjectTracker();
|
||||
var objects = MapObject.FromJson("""[{"id":"p","type":"player_aircraft","heading":90,"x":0.5,"y":0.5}]""", tracker);
|
||||
AssertTrue(objects[0].HeadingRadians.HasValue, "heading exists");
|
||||
AssertEqual(Math.PI / 2, objects[0].HeadingRadians.GetValueOrDefault(), "heading radians");
|
||||
}
|
||||
|
||||
private static void ParsesFlightState()
|
||||
{
|
||||
var state = FlightState.FromJson("""{"TAS, km/h":720,"IAS, km/h":"650","Vy, m/s":36,"H, m":2500}""");
|
||||
AssertEqual(720, state.TrueAirspeedKmh, "tas");
|
||||
AssertEqual(650, state.IndicatedAirspeedKmh, "ias");
|
||||
AssertTrue(state.Mach.HasValue, "mach");
|
||||
AssertTrue(state.ClimbAngleDegrees.HasValue, "climb angle");
|
||||
}
|
||||
|
||||
private static void ParsesBattleMessages()
|
||||
{
|
||||
var messages = BattleMessage.FromJson(
|
||||
"""
|
||||
{
|
||||
"events": [
|
||||
{"text":"<b>Enemy spotted</b>","enemy":true},
|
||||
{"message":"Ally captured A","enemy":false}
|
||||
]
|
||||
}
|
||||
""");
|
||||
|
||||
AssertEqual(2, messages.Count, "message count");
|
||||
AssertEqual("Enemy spotted", messages[0].Text, "cleaned message");
|
||||
AssertTrue(messages[0].Enemy == true, "enemy flag");
|
||||
AssertTrue(messages[1].Enemy == false, "ally flag");
|
||||
}
|
||||
|
||||
private static void AnalyzesCaptureFixtureQuality()
|
||||
{
|
||||
var directory = Path.Combine(Path.GetTempPath(), $"ehwrj-capture-test-{Guid.NewGuid():N}");
|
||||
Directory.CreateDirectory(directory);
|
||||
|
||||
try
|
||||
{
|
||||
File.WriteAllText(Path.Combine(directory, "map_info.json"), """{"grid_size":[10,20],"grid_steps":[1,2]}""");
|
||||
File.WriteAllText(
|
||||
Path.Combine(directory, "map_obj.json"),
|
||||
"""
|
||||
[
|
||||
{"id":"self","type":"player_aircraft","team":"ally","x":0.5,"y":0.5},
|
||||
{"id":"wing","type":"aircraft","team":"ally","x":0.4,"y":0.5},
|
||||
{"id":"bandit","type":"aircraft","team":"enemy","x":0.6,"y":0.5},
|
||||
{"id":"target","type":"bombing_point","team":"enemy","x":0.7,"y":0.5},
|
||||
{"id":"mystery","type":"supply_marker","x":0.2,"y":0.8,"custom_flag":"sample"}
|
||||
]
|
||||
""");
|
||||
File.WriteAllBytes(Path.Combine(directory, "map.img"), [1, 2, 3, 4]);
|
||||
File.WriteAllText(Path.Combine(directory, "state.json"), """{"TAS, km/h":720,"H, m":2500}""");
|
||||
File.WriteAllText(Path.Combine(directory, "hudmsg.json"), """{"events":[{"text":"Enemy spotted","enemy":true}]}""");
|
||||
File.WriteAllText(Path.Combine(directory, "gamechat.json"), """["Good luck"]""");
|
||||
|
||||
var report = CaptureFixtureAnalyzer.AnalyzeDirectory(directory);
|
||||
AssertTrue(report.IsUsableForReplay, "capture replay usable");
|
||||
AssertEqual(5, report.ObjectCount, "capture object count");
|
||||
AssertEqual(1, report.PlayerCount, "capture player count");
|
||||
AssertEqual(2, report.AlliedAircraftCount, "capture allied aircraft count");
|
||||
AssertEqual(1, report.EnemyAircraftCount, "capture enemy aircraft count");
|
||||
AssertEqual(1, report.ObjectiveCount, "capture objective count");
|
||||
AssertEqual(1, report.UnknownObjectCount, "capture unknown object count");
|
||||
AssertTrue(report.ObjectFields.Any(static f => f.Name == "custom_flag" && f.Count == 1), "capture object field coverage");
|
||||
AssertTrue(report.UnknownObjectSamples.Any(static s => s.Contains("supply_marker", StringComparison.Ordinal)), "capture unknown sample");
|
||||
AssertTrue(report.StateFieldNames.Contains("TAS, km/h", StringComparer.Ordinal), "capture state field names");
|
||||
AssertTrue(report.HasSpeedTelemetry, "capture speed telemetry");
|
||||
AssertTrue(report.HasAltitudeTelemetry, "capture altitude telemetry");
|
||||
AssertEqual(1, report.HudMessageCount, "capture hud messages");
|
||||
AssertEqual(1, report.ChatMessageCount, "capture chat messages");
|
||||
AssertTrue(report.ToText().Contains("Replay usable: yes", StringComparison.Ordinal), "capture report text");
|
||||
AssertTrue(report.ToText().Contains("Object field coverage:", StringComparison.Ordinal), "capture field report text");
|
||||
}
|
||||
finally
|
||||
{
|
||||
Directory.Delete(directory, recursive: true);
|
||||
}
|
||||
}
|
||||
|
||||
private static void EnforcesLoopbackNetworkScope()
|
||||
{
|
||||
AssertTrue(LoopbackGuard.IsLoopbackHttp(new Uri("http://127.0.0.1:8111/")), "127 loopback allowed");
|
||||
AssertTrue(LoopbackGuard.IsLoopbackHttp(new Uri("http://localhost:8111/")), "localhost allowed");
|
||||
AssertTrue(LoopbackGuard.IsLoopbackHost("127.0.0.1"), "127 host allowed");
|
||||
AssertTrue(LoopbackGuard.IsLoopbackHost("localhost"), "localhost host allowed");
|
||||
AssertTrue(!LoopbackGuard.IsLoopbackHttp(new Uri("https://127.0.0.1:8111/")), "https rejected");
|
||||
AssertTrue(!LoopbackGuard.IsLoopbackHttp(new Uri("http://example.com:8111/")), "external host rejected");
|
||||
AssertTrue(!LoopbackGuard.IsLoopbackHost("0.0.0.0"), "wildcard host rejected");
|
||||
}
|
||||
|
||||
private static void LocalizesUiTextAndStatus()
|
||||
{
|
||||
var english = UiText.For("en-US");
|
||||
var korean = UiText.For("ko-KR");
|
||||
|
||||
AssertEqual("Language", english.Language, "english language label");
|
||||
AssertEqual("언어", korean.Language, "korean language label");
|
||||
AssertEqual("127.0.0.1:8111 연결됨", korean.FormatStatus("Connected to 127.0.0.1:8111"), "localized connected status");
|
||||
AssertTrue(UiText.LanguageOptions.Any(static o => o.Code == "ko-KR"), "korean language option");
|
||||
}
|
||||
|
||||
private static void ProjectsNormalizedCoordinates()
|
||||
{
|
||||
var point = CoordinateProjector.Project(
|
||||
new MapObject { Id = "p", X = 0.25, Y = 0.75 },
|
||||
MapInfo.Empty,
|
||||
new MapViewport(0, 0, 400, 200));
|
||||
|
||||
AssertTrue(point.HasValue, "projection exists");
|
||||
var projected = point.GetValueOrDefault();
|
||||
AssertEqual(100, projected.X, "projected x");
|
||||
AssertEqual(150, projected.Y, "projected y");
|
||||
}
|
||||
|
||||
private static void ParsesWrappedObjectList()
|
||||
{
|
||||
var tracker = new ObjectTracker();
|
||||
var objects = MapObject.FromJson(
|
||||
"""
|
||||
{
|
||||
"objects": [
|
||||
{"id":"a","icon":"fighter","x":"0.1","y":"0.9"}
|
||||
]
|
||||
}
|
||||
""",
|
||||
tracker);
|
||||
|
||||
AssertEqual(1, objects.Count, "wrapped object count");
|
||||
AssertTrue(objects[0].IsAircraft, "wrapped aircraft classification");
|
||||
AssertEqual(0.1, objects[0].X, "wrapped x");
|
||||
AssertEqual(0.9, objects[0].Y, "wrapped y");
|
||||
}
|
||||
|
||||
private static void ProjectsBoundedCoordinates()
|
||||
{
|
||||
var info = new MapInfo(-100, 100, -50, 50, null, null, null);
|
||||
var point = CoordinateProjector.Project(
|
||||
new MapObject { Id = "p", X = 0, Y = 25 },
|
||||
info,
|
||||
new MapViewport(10, 20, 200, 100));
|
||||
|
||||
AssertTrue(point.HasValue, "bounded projection exists");
|
||||
var projected = point.GetValueOrDefault();
|
||||
AssertEqual(110, projected.X, "bounded x");
|
||||
AssertEqual(95, projected.Y, "bounded y");
|
||||
}
|
||||
|
||||
private static void AssertTrue(bool condition, string name)
|
||||
{
|
||||
if (!condition)
|
||||
{
|
||||
throw new InvalidOperationException($"Assertion failed: {name}");
|
||||
}
|
||||
}
|
||||
|
||||
private static void AssertEqual(double expected, double? actual, string name)
|
||||
{
|
||||
if (!actual.HasValue || Math.Abs(expected - actual.Value) > 0.0001)
|
||||
{
|
||||
throw new InvalidOperationException($"Assertion failed: {name}; expected {expected}, got {actual}");
|
||||
}
|
||||
}
|
||||
|
||||
private static void AssertEqual(double expected, double actual, string name)
|
||||
{
|
||||
if (Math.Abs(expected - actual) > 0.0001)
|
||||
{
|
||||
throw new InvalidOperationException($"Assertion failed: {name}; expected {expected}, got {actual}");
|
||||
}
|
||||
}
|
||||
|
||||
private static void AssertEqual(int expected, int actual, string name)
|
||||
{
|
||||
if (expected != actual)
|
||||
{
|
||||
throw new InvalidOperationException($"Assertion failed: {name}; expected {expected}, got {actual}");
|
||||
}
|
||||
}
|
||||
|
||||
private static void AssertEqual(string expected, string actual, string name)
|
||||
{
|
||||
if (!string.Equals(expected, actual, StringComparison.Ordinal))
|
||||
{
|
||||
throw new InvalidOperationException($"Assertion failed: {name}; expected {expected}, got {actual}");
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user