using System.Collections.Generic; using CounterDrone.Core.Models; using CounterDrone.Core.Simulation; using Xunit; namespace CounterDrone.Core.Tests { public class DroneEntityTests { private TargetConfig CreateConfig(float speed = 120, float altitude = 300) { return new TargetConfig { TargetType = (int)TargetType.Piston, PowerType = (int)PowerType.Piston, TypicalSpeed = speed, TypicalAltitude = altitude, Wingspan = 2.5, Quantity = 1, }; } private List CreateRoute(float fromX, float toX, float alt, float speed) { return new List { new() { PosX = fromX, PosY = alt, PosZ = 0, Altitude = alt, Speed = speed }, new() { PosX = toX, PosY = alt, PosZ = 0, Altitude = alt, Speed = speed }, }; } [Fact] public void InitialPosition_IsFirstWaypoint() { var drone = new DroneEntity("d1", "g1", CreateConfig(), CreateRoute(100, 500, 300, 120), 0, 50, 0, 0, FormationMode.Single); Assert.Equal(100, drone.PosX, 1); Assert.Equal(300, drone.PosY, 1); } [Fact] public void Update_MovesTowardTarget() { var drone = new DroneEntity("d1", "g1", CreateConfig(), CreateRoute(0, 1000, 300, 120), 0, 50, 0, 0, FormationMode.Single); float prevX = drone.PosX; drone.Update(1f, 0, WindDirection.N); Assert.True(drone.PosX > prevX); } [Fact] public void Update_ReachesTarget_StatusChanges() { var drone = new DroneEntity("d1", "g1", CreateConfig(360), CreateRoute(0, 100, 300, 360), 0, 50, 0, 0, FormationMode.Single); drone.Update(2f, 0, WindDirection.N); Assert.Equal(DroneStatus.ReachedTarget, drone.Status); } [Fact] public void Damage_ReducesHp() { var drone = new DroneEntity("d1", "g1", CreateConfig(), CreateRoute(0, 100, 300, 60), 0, 50, 0, 0, FormationMode.Single); drone.ApplyDamage(0.4f); Assert.Equal(0.6f, drone.Hp, 0.01f); } [Fact] public void Damage_ExceedingHp_Destroys() { var drone = new DroneEntity("d1", "g1", CreateConfig(), CreateRoute(0, 100, 300, 60), 0, 50, 0, 0, FormationMode.Single); drone.ApplyDamage(1.5f); Assert.True(drone.Hp <= 0); Assert.Equal(DroneStatus.Destroyed, drone.Status); } [Fact] public void Wind_AffectsPosition() { var drone = new DroneEntity("d1", "g1", CreateConfig(), CreateRoute(0, 1000, 300, 60), 0, 50, 0, 0, FormationMode.Single); float xBefore = drone.PosX; drone.Update(1f, 20f, WindDirection.E); Assert.True(drone.PosX > xBefore + 15f); } [Fact] public void Formation_Lateral_ZOffsetsDiffer() { var route = CreateRoute(0, 100, 300, 60); var d0 = new DroneEntity("d0", "g1", CreateConfig(), route, 0, 50, 0, 0, FormationMode.Formation); var d1 = new DroneEntity("d1", "g1", CreateConfig(), route, 1, 50, 0, 0, FormationMode.Formation); var d2 = new DroneEntity("d2", "g1", CreateConfig(), route, 2, 50, 0, 0, FormationMode.Formation); // 起始Z不同 Assert.NotEqual(d0.PosZ, d1.PosZ); Assert.NotEqual(d1.PosZ, d2.PosZ); // 航点Z不同(偏移持久化到航点) Assert.NotEqual(d0.Route[0].PosZ, d1.Route[0].PosZ); Assert.NotEqual(d0.Route[1].PosZ, d1.Route[1].PosZ); } [Fact] public void Formation_Longitudinal_XOffsetsDiffer() { var route = CreateRoute(0, 100, 300, 60); var d0 = new DroneEntity("d0", "g1", CreateConfig(), route, 0, 0, 0, 100, FormationMode.Formation); var d1 = new DroneEntity("d1", "g1", CreateConfig(), route, 0, 0, 1, 100, FormationMode.Formation); Assert.NotEqual(d0.PosX, d1.PosX); } [Fact] public void Formation_Mixed_2x2() { var route = CreateRoute(0, 100, 300, 60); var d0 = new DroneEntity("d0", "g1", CreateConfig(), route, 0, 50, 0, 100, FormationMode.Formation); var d1 = new DroneEntity("d1", "g1", CreateConfig(), route, 1, 50, 0, 100, FormationMode.Formation); var d2 = new DroneEntity("d2", "g1", CreateConfig(), route, 0, 50, 1, 100, FormationMode.Formation); var d3 = new DroneEntity("d3", "g1", CreateConfig(), route, 1, 50, 1, 100, FormationMode.Formation); Assert.NotEqual(d0.PosZ, d1.PosZ); // lateral Assert.NotEqual(d0.PosX, d2.PosX); // longitudinal } [Fact] public void Swarm_ProducesRandomOffsets() { var route = CreateRoute(0, 100, 300, 60); var d0 = new DroneEntity("d0", "g1", CreateConfig(), route, 0, 30, 0, 0, FormationMode.Swarm); var d1 = new DroneEntity("d1", "g1", CreateConfig(), route, 1, 30, 0, 0, FormationMode.Swarm); Assert.True(d0.PosX != d1.PosX || d0.PosZ != d1.PosZ); } } }