using NeonSprawl.Server.Game.PositionState; using Xunit; namespace NeonSprawl.Server.Tests.Game.PositionState; public class MoveCommandValidationTests { private static MovementValidationOptions Rules( double maxH, double maxV, bool bounds = false, bool horizontalEnabled = true) => new() { HorizontalStepEnabled = horizontalEnabled, MaxHorizontalStep = maxH, MaxVerticalStep = maxV, DistrictBoundsEnabled = bounds, DistrictMinX = 0, DistrictMaxX = 10, DistrictMinY = 0, DistrictMaxY = 10, DistrictMinZ = 0, DistrictMaxZ = 10, }; [Fact] public void TryValidate_ShouldAllow_WhenHorizontalExactlyAtMax() { var from = new PositionSnapshot(0, 0, 0, 0); var to = new PositionVector { X = 5, Y = 0, Z = 0 }; Assert.True(MoveCommandValidation.TryValidate(from, to, Rules(5, 10), out var code)); Assert.Equal("", code); } [Fact] public void TryValidate_ShouldRejectHorizontal_WhenJustOutsideMax() { var from = new PositionSnapshot(0, 0, 0, 0); var to = new PositionVector { X = 5 + 1e-6, Y = 0, Z = 0 }; Assert.False(MoveCommandValidation.TryValidate(from, to, Rules(5, 10), out var code)); Assert.Equal(MoveCommandReasonCodes.HorizontalStepExceeded, code); } [Fact] public void TryValidate_ShouldAllow_WhenVerticalExactlyAtMax() { var from = new PositionSnapshot(0, 1, 0, 0); var to = new PositionVector { X = 0, Y = 2.5, Z = 0 }; Assert.True(MoveCommandValidation.TryValidate(from, to, Rules(10, 1.5), out _)); } [Fact] public void TryValidate_ShouldRejectVertical_WhenJustOutsideMax() { var from = new PositionSnapshot(0, 1, 0, 0); var to = new PositionVector { X = 0, Y = 2.5 + 1e-9, Z = 0 }; Assert.False(MoveCommandValidation.TryValidate(from, to, Rules(10, 1.5), out var code)); Assert.Equal(MoveCommandReasonCodes.VerticalStepExceeded, code); } [Fact] public void TryValidate_ShouldPreferHorizontalReason_WhenBothWouldFail() { var from = new PositionSnapshot(0, 0, 0, 0); var to = new PositionVector { X = 100, Y = 100, Z = 0 }; Assert.False(MoveCommandValidation.TryValidate(from, to, Rules(1, 1), out var code)); Assert.Equal(MoveCommandReasonCodes.HorizontalStepExceeded, code); } [Fact] public void TryValidate_ShouldAllow_WhenHorizontalDisabled() { var from = new PositionSnapshot(0, 0, 0, 0); var to = new PositionVector { X = 1000, Y = 0, Z = 1000 }; Assert.True(MoveCommandValidation.TryValidate(from, to, Rules(1, 10, horizontalEnabled: false), out var code)); Assert.Equal("", code); } [Fact] public void TryValidate_ShouldIgnoreBounds_WhenDistrictDisabled() { var from = new PositionSnapshot(0, 0, 0, 0); var to = new PositionVector { X = -50, Y = 0, Z = 0 }; Assert.True(MoveCommandValidation.TryValidate(from, to, Rules(100, 100, bounds: false), out _)); } [Fact] public void TryValidate_ShouldRejectOutOfBounds_WhenDistrictEnabled() { var from = new PositionSnapshot(5, 5, 5, 0); var to = new PositionVector { X = 5, Y = 5, Z = 11 }; Assert.False(MoveCommandValidation.TryValidate(from, to, Rules(20, 20, bounds: true), out var code)); Assert.Equal(MoveCommandReasonCodes.OutOfBounds, code); } [Fact] public void TryValidate_ShouldAllowOnBoundsSurface_WhenDistrictEnabled() { var from = new PositionSnapshot(5, 5, 5, 0); var to = new PositionVector { X = 10, Y = 10, Z = 10 }; Assert.True(MoveCommandValidation.TryValidate(from, to, Rules(20, 20, bounds: true), out _)); } }