using FluentAssertions; using Oms.Application.Dtos.Orders; using Oms.Application.Shared; using Oms.Domain.ChannelsAggregate.Entities; namespace Oms.Tests; public class GeoPartitionMatcherTests { [Fact] public void ZipPrefix_LongerPrefixWins() { var rules = CompileEntities( Zip("US", "9"), Zip("US", "90")); var hit = GeoPartitionMatcher.MatchEntity(rules, "US", "CA", "LA", "90001"); hit!.LeftPostcode.Should().Be("90"); } [Fact] public void ZipPresent_ShouldSkipStateAndCity() { var rules = CompileEntities( State("US", "CA"), City("US", "CA", "LA")); GeoPartitionMatcher.MatchEntity(rules, "US", "CA", "LA", "90001").Should().BeNull(); } [Fact] public void ZipEmpty_StateExact_ShouldHit() { var rules = CompileEntities( Zip("US", "90"), State("US", "CA")); var hit = GeoPartitionMatcher.MatchEntity(rules, "US", "CA", "LA", null); hit!.Province.Should().Be("CA"); hit.LeftPostcode.Should().BeNull(); } [Fact] public void ZipEmpty_CityRequiresState() { var rules = CompileEntities(City("US", "CA", "LOSANGELES")); GeoPartitionMatcher.MatchEntity(rules, "US", "NY", "LOSANGELES", null).Should().BeNull(); GeoPartitionMatcher.MatchEntity(rules, "US", "CA", "LOSANGELES", null).Should().NotBeNull(); } [Fact] public void ZipRange_ShouldBeClosedInterval() { var rules = CompileEntities(Range("US", "90000", "90199")); GeoPartitionMatcher.IsHit(rules, "US", null, null, "90000").Should().BeTrue(); GeoPartitionMatcher.IsHit(rules, "US", null, null, "90199").Should().BeTrue(); GeoPartitionMatcher.IsHit(rules, "US", null, null, "90200").Should().BeFalse(); } [Fact] public void ZipRange_NumericDifferentLength_ShouldCompareAsNumbers() { var rules = CompileEntities(Range("US", "9", "10")); GeoPartitionMatcher.IsHit(rules, "US", null, null, "9").Should().BeTrue(); GeoPartitionMatcher.IsHit(rules, "US", null, null, "10").Should().BeTrue(); GeoPartitionMatcher.IsHit(rules, "US", null, null, "8").Should().BeFalse(); GeoPartitionMatcher.IsHit(rules, "US", null, null, "11").Should().BeFalse(); } [Fact] public void ZipRange_LeadingZeros_ShouldKeepZipWidth() { var rules = CompileEntities(Range("US", "02100", "02200")); GeoPartitionMatcher.IsHit(rules, "US", null, null, "02115").Should().BeTrue(); GeoPartitionMatcher.IsHit(rules, "US", null, null, "02100").Should().BeTrue(); GeoPartitionMatcher.IsHit(rules, "US", null, null, "02200").Should().BeTrue(); GeoPartitionMatcher.IsHit(rules, "US", null, null, "02099").Should().BeFalse(); } [Fact] public void ZipRange_AlphaNumericPrefix_ShouldCompareNumericTail() { var rules = CompileEntities(Range("GB", "YO0", "YO9")); GeoPartitionMatcher.IsHit(rules, "GB", null, null, "YO0").Should().BeTrue(); GeoPartitionMatcher.IsHit(rules, "GB", null, null, "YO9").Should().BeTrue(); GeoPartitionMatcher.IsHit(rules, "GB", null, null, "YO10").Should().BeFalse(); } [Fact] public void CountryFallback_ShouldHitWhenZipMisses() { var rules = CompileEntities( Range("US", "10000", "19999"), CountryOnly("US")); var hit = GeoPartitionMatcher.MatchEntity(rules, "US", "CA", null, "90001"); hit!.LeftPostcode.Should().BeNull(); hit.Province.Should().BeNull(); } [Fact] public void ThreePartitions_ShouldNotInterfere() { var black = CompileEntities(Range("US", "90000", "90010")); var remote = CompileEntities(Range("US", "05999", "06999")); var price = CompileEntities(Zip("US", "90")); GeoPartitionMatcher.IsHit(black, "US", null, null, "90005").Should().BeTrue(); GeoPartitionMatcher.IsHit(remote, "US", null, null, "90005").Should().BeFalse(); GeoPartitionMatcher.MatchEntity(price, "US", null, null, "90005")!.LeftPostcode.Should().Be("90"); } [Fact] public void SnapshotLegacyLeftOpenFormat_ShouldParseAsClosed() { var infos = new List { new() { Id = 1, CountryCode = "US", PostcodeRange = "(00000,50000]" } }; GeoPartitionMatcher.IsHit(infos, "US", null, null, "00000").Should().BeTrue(); GeoPartitionMatcher.IsHit(infos, "US", null, null, "50000").Should().BeTrue(); GeoPartitionMatcher.IsHit(infos, "US", null, null, "50001").Should().BeFalse(); } [Fact] public void PrepareEntity_PrefixKeepsRightEmpty() { var entity = new KpiAreaInfo { CountryCode = "us", LeftPostcode = "90" }; GeoPartitionMatcher.PrepareEntity(entity); entity.LeftPostcode.Should().Be("90"); entity.RightPostcode.Should().BeNull(); entity.MatchDim.Should().Be("zip"); entity.MatchMode.Should().Be("prefix"); entity.PrefixLen.Should().Be(2); GeoPartitionMatcher.Describe(entity).MatchDim.Should().Be("zip"); GeoPartitionMatcher.Describe(entity).MatchMode.Should().Be("prefix"); } [Fact] public void PrepareEntity_CityWithoutState_ShouldThrow() { var entity = new KpiAreaInfo { CountryCode = "US", City = "LA" }; var act = () => GeoPartitionMatcher.PrepareEntity(entity); act.Should().Throw().WithMessage("*州*"); } [Theory] [InlineData("BT9 7AA", "BT97AA")] [InlineData("bt97aa", "BT97AA")] [InlineData("BT9-7AA", "BT97AA")] [InlineData("B46 3AB", "B463AB")] [InlineData(" b46 3ab ", "B463AB")] public void NormalizeZip_AlphanumericWithSpaces_ShouldStripAndUpper(string raw, string expected) { GeoPartitionMatcher.NormalizeZip(raw).Should().Be(expected); } [Fact] public void Gb_Bt97aa_LongerOutwardPrefixWins() { var rules = CompileEntities( Zip("GB", "BT"), Zip("GB", "BT9"), Zip("GB", "BT97"), Zip("GB", "B")); var hit = GeoPartitionMatcher.MatchEntity(rules, "GB", "NIR", "Belfast", "BT9 7AA"); hit!.LeftPostcode.Should().Be("BT97"); GeoPartitionMatcher.MatchEntity(rules, "gb", null, null, "BT97AA")!.LeftPostcode.Should().Be("BT97"); } [Fact] public void Gb_Bt97aa_ExactIgnoresSpaces() { var rules = CompileEntities(Exact("GB", "BT9 7AA")); GeoPartitionMatcher.IsHit(rules, "GB", null, null, "BT97AA").Should().BeTrue(); GeoPartitionMatcher.IsHit(rules, "GB", null, null, "BT9 7AA").Should().BeTrue(); GeoPartitionMatcher.IsHit(rules, "GB", null, null, "BT9 7AB").Should().BeFalse(); GeoPartitionMatcher.IsHit(rules, "GB", null, null, "BT98AA").Should().BeFalse(); } [Fact] public void Gb_Bt97aa_ClosedAlphanumericRange() { var rules = CompileEntities(Range("GB", "BT90AA", "BT99ZZ")); GeoPartitionMatcher.IsHit(rules, "GB", null, null, "BT97AA").Should().BeTrue(); GeoPartitionMatcher.IsHit(rules, "GB", null, null, "BT9 7AA").Should().BeTrue(); GeoPartitionMatcher.IsHit(rules, "GB", null, null, "BT90AA").Should().BeTrue(); GeoPartitionMatcher.IsHit(rules, "GB", null, null, "BT99ZZ").Should().BeTrue(); GeoPartitionMatcher.IsHit(rules, "GB", null, null, "BT8 1AA").Should().BeFalse(); GeoPartitionMatcher.IsHit(rules, "GB", null, null, "E1 6AN").Should().BeFalse(); } [Fact] public void Ca_B463ab_PrefixAndSpaceVariants() { var rules = CompileEntities( Zip("CA", "B"), Zip("CA", "B4"), Zip("CA", "B46"), Zip("CA", "B463")); var hit = GeoPartitionMatcher.MatchEntity(rules, "CA", "ON", "Toronto", "B46 3AB"); hit!.LeftPostcode.Should().Be("B463"); GeoPartitionMatcher.IsHit(rules, "CA", null, null, "B463AB").Should().BeTrue(); GeoPartitionMatcher.MatchEntity(rules, "CA", null, null, "B47 3AB")!.LeftPostcode.Should().Be("B4"); GeoPartitionMatcher.IsHit(rules, "CA", null, null, "M5V 2T6").Should().BeFalse(); } [Fact] public void Ca_B463ab_ExactAndRange() { var exact = CompileEntities(Exact("CA", "B46 3AB")); var range = CompileEntities(Range("CA", "B460AA", "B469ZZ")); GeoPartitionMatcher.IsHit(exact, "CA", null, null, "B46 3AB").Should().BeTrue(); GeoPartitionMatcher.IsHit(exact, "CA", null, null, "B463AB").Should().BeTrue(); GeoPartitionMatcher.IsHit(exact, "CA", null, null, "B46 3AC").Should().BeFalse(); GeoPartitionMatcher.IsHit(range, "CA", null, null, "B46 3AB").Should().BeTrue(); GeoPartitionMatcher.IsHit(range, "CA", null, null, "B46 0AA").Should().BeTrue(); GeoPartitionMatcher.IsHit(range, "CA", null, null, "B47 3AB").Should().BeFalse(); } [Fact] public void GbAndCa_SameLookingPostcode_CountryMustMatch() { var gb = CompileEntities(Zip("GB", "B46"), Exact("GB", "BT97AA")); var ca = CompileEntities(Zip("CA", "B46")); GeoPartitionMatcher.IsHit(gb, "GB", null, null, "B46 3AB").Should().BeTrue(); GeoPartitionMatcher.IsHit(gb, "CA", null, null, "B46 3AB").Should().BeFalse(); GeoPartitionMatcher.IsHit(ca, "CA", null, null, "B46 3AB").Should().BeTrue(); GeoPartitionMatcher.IsHit(ca, "GB", null, null, "BT97AA").Should().BeFalse(); GeoPartitionMatcher.IsHit(gb, "GB", null, null, "BT97AA").Should().BeTrue(); } [Fact] public void Gb_Bt97aa_ZipPresent_ShouldSkipState() { var rules = CompileEntities( State("GB", "NIR"), Zip("GB", "BT1")); GeoPartitionMatcher.MatchEntity(rules, "GB", "NIR", "Belfast", "BT97AA").Should().BeNull(); GeoPartitionMatcher.MatchEntity(rules, "GB", "NIR", "Belfast", null)!.Province.Should().Be("NIR"); } private static List CompileEntities(params KpiAreaInfo[] items) { foreach (var item in items) { GeoPartitionMatcher.PrepareEntity(item); } return items.ToList(); } private static KpiAreaInfo Zip(string country, string prefix) => new() { CountryCode = country, LeftPostcode = prefix }; private static KpiAreaInfo Exact(string country, string postcode) => new() { CountryCode = country, LeftPostcode = postcode, RightPostcode = postcode }; private static KpiAreaInfo Range(string country, string left, string right) => new() { CountryCode = country, LeftPostcode = left, RightPostcode = right }; private static KpiAreaInfo State(string country, string state) => new() { CountryCode = country, Province = state }; private static KpiAreaInfo City(string country, string state, string city) => new() { CountryCode = country, Province = state, City = city }; private static KpiAreaInfo CountryOnly(string country) => new() { CountryCode = country }; }