XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet

XFEEDToolBox

【WinUI】XFE精英危险工具箱

公开
关注 0 Fork 0 Star 0
UTF-8
using XFEExtension.NetCore.XUnit.Attributes;
using XFEEDToolBox.Core.Data;
using XFEEDToolBox.Core.Models;
using XFEEDToolBox.Core.Planning;

namespace XFEEDToolBox.Test;

[TestFixture]
public class CargoTests
{
    [Test] public void MatchesIndependentExhaustiveSearch()
    {
        var random = new Random(71903);
        for (var trial = 0; trial < 160; trial++)
        {
            var goods = Enumerable.Range(0, 4).Select(i => { var buy = random.Next(1, 40); return new CargoCandidate("g" + i, "g" + i, buy, buy + random.Next(1, 25), random.Next(1, 6)); }).ToArray();
            var capacity = random.Next(1, 10); var cash = random.Next(1, 180); decimal optimum = 0;
            for (var a = 0; a <= goods[0].Limit; a++) for (var b = 0; b <= goods[1].Limit; b++)
                for (var c = 0; c <= goods[2].Limit; c++) for (var d = 0; d <= goods[3].Limit; d++)
                {
                    var quantities = new[] { a, b, c, d };
                    if (quantities.Sum() <= capacity && quantities.Select((q, i) => (decimal)q * goods[i].BuyPrice).Sum() <= cash)
                        optimum = Math.Max(optimum, quantities.Select((q, i) => (decimal)q * goods[i].ProfitPerTon).Sum());
                }
            var result = CargoOptimizer.Optimize(goods, capacity, cash, 20000);
            Check.Equal(optimum, result.Profit);
            Check.That(result.ProvenOptimal, "Small problem should finish within budget");
            Check.That(result.Orders.Sum(o => o.Cost) <= cash && result.Orders.Sum(o => o.Quantity) <= capacity);
        }
    }
    [Test] public void MixesCheapAndExpensiveCargoUnderCashLimit()
    {
        var result = CargoOptimizer.Optimize([new("a", "A", 90, 190, 10), new("b", "B", 10, 80, 10)], 10, 180);
        Check.Equal(730m, result.Profit);
        Check.Equal(2, result.Orders.Count);
    }
    [Test] public void RespectsSupplyAndDemandLimit() => Check.Equal(2, CargoOptimizer.Optimize([new("a", "A", 1, 100, 2)], 100, 100).Orders.Single().Quantity);
    [Test] public void UsesDecimalForLargeTransactionValues() => Check.Equal(1000000000000m, CargoOptimizer.Optimize([new("a", "A", 10000000000, 20000000000, 100)], 100, 1000000000000m).Profit);
    [Test] public void MarksBudgetLimitedResult() => Check.That(!CargoOptimizer.Optimize([new("a", "A", 9, 15, 20), new("b", "B", 2, 5, 20)], 20, 90, 1).ProvenOptimal);
    [Test] public void EmptyBudgetHasNoCargo() => Check.Equal(0, CargoOptimizer.Optimize([new("a", "A", 1, 2, 100)], 100, 0).Orders.Count);
    [Test] public void CancellationIsObserved()
    {
        using var cancel = new CancellationTokenSource(); cancel.Cancel();
        Check.Throws<OperationCanceledException>(() => CargoOptimizer.Optimize([new("a", "A", 1, 2, 10)], 10, 10, token: cancel.Token));
    }
}

[TestFixture]
public class PlannerTests
{
    private static RoutePlan Run(MarketBook? book = null, PlannerRequest? request = null) => new TradeRoutePlanner().Calculate(book ?? Check.TwoStations, request ?? Check.Request);
    [Test] public void RollsProfitIntoNextStationCash()
    {
        var result = Run(request: Check.Request with { Loop = true });
        Check.Equal(2, result.Legs.Count); Check.That(result.IsLoop);
        Check.Equal(1600m, result.NetProfit); Check.Equal(11600m, result.Legs[^1].CashAfter);
    }
    [Test] public void ReserveIsNeverSpent()
    {
        var result = Run(request: Check.Request with { Credits = 1500, ReserveCredits = 1000 });
        Check.That(result.Legs.Count > 0); Check.That(result.Legs[0].Orders.Sum(o => o.Cost) <= 500);
    }
    [Test] public void StaleQuotesDoNotBecomeProfitableRoutes()
    {
        var b = Check.TwoStations; b = b with { Quotes = b.Quotes.Select(q => q with { CollectedAt = DateTimeOffset.UtcNow.AddDays(-2) }).ToArray() };
        Check.Equal(0, Run(b).Legs.Count);
    }
    [Test] public void FutureQuotesAreRejected()
    {
        var b = Check.TwoStations with { Quotes = Check.TwoStations.Quotes.Select(q => q with { CollectedAt = DateTimeOffset.UtcNow.AddHours(2) }).ToArray() };
        Check.Equal(0, Run(b).Legs.Count);
    }
    [Test] public void IllegalGoodsAreExcluded()
    {
        var b = Check.TwoStations with { Quotes = Check.TwoStations.Quotes.Select(q => q with { Illegal = true }).ToArray() };
        Check.Equal(0, Run(b).Legs.Count);
    }
    [Test] public void LargeShipsCannotUseMediumPads()
    {
        var b = Check.TwoStations with { Stations = [Check.Station(1), Check.Station(2, 10) with { MaxLandingPad = LandingPad.Medium }] };
        Check.Equal(0, Run(b).Legs.Count);
    }
    [Test] public void ConservativeRiskExcludesAnarchy()
    {
        var b = Check.TwoStations with { Stations = [Check.Station(1), Check.Station(2, 10) with { Security = SecurityLevel.Anarchy }] };
        Check.Equal(0, Run(b, Check.Request with { Risk = RiskTolerance.Conservative }).Legs.Count);
    }
    [Test] public void UnknownMetadataIsExcludedByDefault()
    {
        var b = Check.TwoStations with { Stations = [Check.Station(1), Check.Station(2, 10) with { DistanceToArrivalLs = null }] };
        Check.Equal(0, Run(b).Legs.Count);
    }
    [Test] public void ExcludesPermitRestrictedStations()
    {
        var b = Check.TwoStations with { Stations = [Check.Station(1), Check.Station(2, 10) with { RequiresPermit = true }] };
        Check.Equal(0, Run(b).Legs.Count);
    }
    [Test] public void CountsSupplyAcrossRepeatedLoopVisits()
    {
        var b = Check.TwoStations with { Quotes = Check.TwoStations.Quotes.Select(q => q with { Supply = 2, Demand = 3 }).ToArray() };
        var result = Run(b, Check.Request with { Loop = true, MaxLegs = 6, Objective = RouteObjective.TotalProfit });
        foreach (var group in result.Legs.SelectMany(l => l.Orders.Select(o => (l.From.MarketId, o))).GroupBy(x => (x.MarketId, x.o.Commodity))) Check.That(group.Sum(x => x.o.Quantity) <= 2);
    }
    [Test] public void DeductsOperatingCosts()
    {
        var result = Run(request: Check.Request with { MaxLegs = 1, CostPerJump = 100 });
        Check.Equal(700m, result.NetProfit);
    }
    [Test] public void RejectsZeroJumpRange() => Check.Throws<ArgumentException>(() => Run(request: Check.Request with { LadenJumpRangeLy = 0 }));
    [Test] public void RejectsAllCashReserved() => Check.Throws<ArgumentException>(() => Run(request: Check.Request with { ReserveCredits = 10000 }));
    [Test] public void DemoLoopContainsMixedCargo()
    {
        var p = DemoData.Player;
        var result = Run(DemoData.CreateBook(), Check.Request with { StartMarketId = p.MarketId!.Value, Credits = p.Credits!.Value, ReserveCredits = p.Rebuy, CargoCapacity = p.FreeCargo, Loop = true, MaxLegs = 4 });
        Check.That(result.NetProfit > 0 && result.IsLoop); Check.That(result.Legs.Any(l => l.Orders.Count > 1));
    }
    [Test] public void ReportsInputTruncation() => Check.That(Run(Check.TwoStations with { Truncated = true }).Warnings.Any(w => w.Contains("截断")));
}