using System; using System.Collections.Generic; using Bogatyri.World; using NUnit.Framework; // Тесты модулей без Unity-сцены. Запуск: Window -> General -> Test Runner -> EditMode -> Run All. // Список тестов, которые команда решила добавить (план от 19.10.2026), -- в Docs/Modules/README.md. public class ModuleTests { [SetUp] public void ResetCore() { GameServices.Clear(); EventBus.Clear(); SaveRegistry.Clear(); } // ---------------- Армия: один солдат не может быть в двух местах ---------------- [Test] public void ArmyMath_TakeProportional_NeverDoubleCountsOrLosesSoldiers() { var rnd = new Random(1); for (int run = 0; run < 2000; run++) { var troops = new int[4]; int total = 0; for (int i = 0; i < 4; i++) { troops[i] = rnd.Next(0, 50); total += troops[i]; } if (total == 0) continue; int count = rnd.Next(1, total + 1); int[] taken = ArmyMath.TakeProportional(troops, count); int sum = 0; for (int i = 0; i < 4; i++) { Assert.That(taken[i], Is.InRange(0, troops[i]), "нельзя взять больше, чем есть в типе"); sum += taken[i]; } Assert.AreEqual(count, sum, "в поход уходит ровно столько, сколько просили"); } } [Test] public void ArmyMath_TakeMoreThanExists_ReturnsNull() { Assert.IsNull(ArmyMath.TakeProportional(new[] { 1, 1, 0, 0 }, 3)); } [Test] public void ArmyMath_SplitSurvivors_ReturnsExactlySurvivors() { int[] back = ArmyMath.SplitSurvivors(new[] { 10, 5, 0, 1 }, 9); int sum = 0; foreach (var b in back) { Assert.GreaterOrEqual(b, 0); sum += b; } Assert.AreEqual(9, sum); } // ---------------- Боевая система ---------------- [Test] public void Battle_MoreSoldiersWins_TieGoesToDefender() { var battle = new BattleSystem(); Assert.IsTrue(battle.Resolve(new BattleRequest { Attackers = 30, Defenders = 20 }).Victory); Assert.IsFalse(battle.Resolve(new BattleRequest { Attackers = 20, Defenders = 20 }).Victory); Assert.AreEqual(20, battle.Resolve(new BattleRequest { Attackers = 30, Defenders = 20 }).Survivors); } // ---------------- Глобальный мир: поход до конца, в том числе офлайн ---------------- [Test] public void World_MarchResolvesAndReturns_EvenAfterLongOffline() { var world = new WorldSimulation(WorldMapLayout.CreateDefault(), new BattleSystem()); MarchData march = world.StartMarch("p", WorldMapLayout.PlayerCityId, WorldMapLayout.BossCastleId, 30, 0, out string error); Assert.IsNotNull(march, error); var events = new List(); world.Advance(3_600_000, events); // игра была закрыта час Assert.AreEqual(2, events.Count, "бой и возвращение за один вызов"); Assert.AreEqual(WorldEventType.BattleResolved, events[0].Type); Assert.AreEqual(WorldEventType.MarchReturned, events[1].Type); Assert.AreEqual(0, world.Marches.Count); } // ---------------- Ядро ---------------- [Test] public void EventBus_FailingSubscriberDoesNotBreakOthers() { int delivered = 0; Exception caught = null; EventBus.OnHandlerError = e => caught = e; EventBus.Subscribe(_ => throw new InvalidOperationException("boom")); EventBus.Subscribe(_ => delivered++); EventBus.Publish(new MarchReturnedEvent { Survivors = 5 }); Assert.AreEqual(1, delivered); Assert.IsNotNull(caught); } [Test] public void GameServices_MissingModule_ThrowsClearError() { Assert.Throws(() => GameServices.Get()); Assert.IsFalse(GameServices.TryGet(out IArmy _)); } }