using TheLivingWorld.Core.Simulation; namespace TheLivingWorld.Tests; public sealed class ClimateTests { [Theory] [InlineData(1.35, ClimateKind.Equatorial)] // Singapore [InlineData(13.75, ClimateKind.Savanna)] // Bangkok [InlineData(30.05, ClimateKind.HotDesert)] // Cairo [InlineData(37.98, ClimateKind.Mediterranean)] // Athens [InlineData(51.51, ClimateKind.Oceanic)] // London [InlineData(55.75, ClimateKind.CentralEuropean)] // Moscow [InlineData(62.03, ClimateKind.Siberian)] // Yakutsk [InlineData(78.22, ClimateKind.Tundra)] // Svalbard public void FromLatitude_lands_on_the_expected_band(double latitude, ClimateKind expected) { Assert.Equal(expected, ClimateCatalog.FromLatitude(latitude)); } [Fact] public void FromLatitude_ignores_the_hemisphere() { for (var degrees = 0.0; degrees <= 90.0; degrees += 0.5) Assert.Equal(ClimateCatalog.FromLatitude(degrees), ClimateCatalog.FromLatitude(-degrees)); } [Fact] public void FromLatitude_is_monotonic_from_the_equator_to_the_pole() { // The bands must not interleave: walking north should never hand back a warmer preset than the last. var previous = -1; for (var degrees = 0.0; degrees <= 90.0; degrees += 0.25) { var index = ClimateCatalog.All .Select(static (preset, i) => (preset.Kind, Index: i)) .First(entry => entry.Kind == ClimateCatalog.FromLatitude(degrees)) .Index; Assert.True(index >= previous, $"Latitude {degrees} stepped backwards in the catalogue order."); previous = index; } } [Fact] public void Every_preset_is_reachable_and_self_consistent() { Assert.Equal(12, ClimateCatalog.All.Count); foreach (var kind in Enum.GetValues()) { var preset = ClimateCatalog.Get(kind); Assert.Equal(kind, preset.Kind); Assert.False(string.IsNullOrWhiteSpace(preset.Label)); Assert.False(string.IsNullOrWhiteSpace(preset.KoppenCode)); Assert.False(string.IsNullOrWhiteSpace(preset.Example)); Assert.InRange(preset.Humidity, 0f, 1f); Assert.InRange(preset.Wetness, 0f, 1f); Assert.InRange(preset.Storminess, 1, WeatherSystem.MaxSystems); } } [Fact] public void Presets_the_latitude_rule_cannot_reach_are_reported_as_such() { var reachable = Enumerable.Range(0, 91) .Select(static degrees => ClimateCatalog.FromLatitude(degrees)) .ToHashSet(); foreach (var preset in ClimateCatalog.All) Assert.Equal(reachable.Contains(preset.Kind), ClimateCatalog.IsInferable(preset.Kind)); } [Fact] public void Band_limits_are_ordered_and_reproduce_the_latitude_rule() { // The create form re-derives FromLatitude on the client from exactly these limits, walking the list // in order, so they have to be ascending and they have to agree at every latitude. var banded = ClimateCatalog.All .Select(static preset => (preset.Kind, Limit: ClimateCatalog.BandLimit(preset.Kind))) .Where(static entry => entry.Limit is not null) .Select(static entry => (entry.Kind, Limit: entry.Limit!.Value)) .ToArray(); Assert.NotEmpty(banded); Assert.Equal(banded.OrderBy(static entry => entry.Limit).ToArray(), banded); for (var degrees = 0.0; degrees <= 90.0; degrees += 0.25) { var expected = banded.FirstOrDefault(entry => degrees < entry.Limit, banded[^1]).Kind; Assert.Equal(expected, ClimateCatalog.FromLatitude(degrees)); } } [Fact] public void Get_rejects_an_undefined_climate() { Assert.Throws(() => ClimateCatalog.Get((ClimateKind)99)); } }