Files
the-living-world/tests/TheLivingWorld.Tests/ClimateTests.cs
T

102 lines
3.9 KiB
C#

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<ClimateKind>())
{
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<ArgumentOutOfRangeException>(() => ClimateCatalog.Get((ClimateKind)99));
}
}