using FluentValidation.TestHelper; using SatelliteProvider.Api.DTOs; using SatelliteProvider.Api.Validators; namespace SatelliteProvider.Tests.Validators; // AZ-811: unit tests for GetTileByLatLonQueryValidator. One Theory per RuleFor // covering boundary + out-of-range. Unknown-query-param rejection is tested // at the integration layer (GetTileByLatLonValidationTests) — there's no // pure-unit equivalent because the filter runs against HttpContext.Request.Query. public class GetTileByLatLonQueryValidatorTests { private readonly GetTileByLatLonQueryValidator _validator; public GetTileByLatLonQueryValidatorTests() { GlobalValidatorConfig.ApplyOnce(); _validator = new GetTileByLatLonQueryValidator(); } [Theory] [InlineData(-90.001)] [InlineData(90.001)] [InlineData(180.0)] public void Validate_LatOutOfRange_FailsRangeRule(double lat) { // Arrange var query = new GetTileByLatLonQuery(lat, 37.647063, 18); // Act var result = _validator.TestValidate(query); // Assert result.ShouldHaveValidationErrorFor("lat"); } [Fact] public void Validate_LatNull_FailsNotNullRule() { // Arrange var query = new GetTileByLatLonQuery(null, 37.647063, 18); // Act var result = _validator.TestValidate(query); // Assert — CascadeMode.Stop ensures NotNull short-circuits the range // rule, so the caller sees only `"\`lat\` is required."` not also the // range error against a null sentinel. result.ShouldHaveValidationErrorFor("lat").WithErrorMessage("`lat` is required."); } [Theory] [InlineData(-90.0)] [InlineData(0.0)] [InlineData(47.461747)] [InlineData(90.0)] public void Validate_LatAtOrInsideBounds_Passes(double lat) { // Arrange var query = new GetTileByLatLonQuery(lat, 37.647063, 18); // Act var result = _validator.TestValidate(query); // Assert result.ShouldNotHaveValidationErrorFor("lat"); } [Theory] [InlineData(-180.001)] [InlineData(180.001)] [InlineData(360.0)] public void Validate_LonOutOfRange_FailsRangeRule(double lon) { // Arrange var query = new GetTileByLatLonQuery(47.461747, lon, 18); // Act var result = _validator.TestValidate(query); // Assert result.ShouldHaveValidationErrorFor("lon"); } [Fact] public void Validate_LonNull_FailsNotNullRule() { // Arrange var query = new GetTileByLatLonQuery(47.461747, null, 18); // Act var result = _validator.TestValidate(query); // Assert result.ShouldHaveValidationErrorFor("lon").WithErrorMessage("`lon` is required."); } [Theory] [InlineData(-180.0)] [InlineData(0.0)] [InlineData(37.647063)] [InlineData(180.0)] public void Validate_LonAtOrInsideBounds_Passes(double lon) { // Arrange var query = new GetTileByLatLonQuery(47.461747, lon, 18); // Act var result = _validator.TestValidate(query); // Assert result.ShouldNotHaveValidationErrorFor("lon"); } [Theory] [InlineData(-1)] [InlineData(23)] [InlineData(100)] public void Validate_ZoomOutOfRange_FailsRangeRule(int zoom) { // Arrange var query = new GetTileByLatLonQuery(47.461747, 37.647063, zoom); // Act var result = _validator.TestValidate(query); // Assert result.ShouldHaveValidationErrorFor("zoom"); } [Fact] public void Validate_ZoomNull_FailsNotNullRule() { // Arrange var query = new GetTileByLatLonQuery(47.461747, 37.647063, null); // Act var result = _validator.TestValidate(query); // Assert result.ShouldHaveValidationErrorFor("zoom").WithErrorMessage("`zoom` is required."); } [Theory] [InlineData(0)] [InlineData(18)] [InlineData(22)] public void Validate_ZoomAtOrInsideBounds_Passes(int zoom) { // Arrange var query = new GetTileByLatLonQuery(47.461747, 37.647063, zoom); // Act var result = _validator.TestValidate(query); // Assert result.ShouldNotHaveValidationErrorFor("zoom"); } }